generateMappedSwaggerTypes function
Generates mapped types from Swagger specifications.
This function processes each file in fileList to:
- Parse the Swagger specification into structured objects.
- Extract namespaces, endpoints, and operation details.
- Generate Dart types and mappings for API endpoints.
- Ensure compliance with Dart naming conventions.
Additionally, it:
- Validates namespaces and operation IDs.
- Formats the generated files using
dart format.
Parameters:
fileList: A list of TFileInfo containing parsed Swagger specifications and file metadata.targetPath: The directory where the generated files should be stored.
Implementation
Future<void> generateMappedSwaggerTypes({
required List<TFileInfo> fileList,
required String targetPath,
}) async {
for (final fileInfo in fileList) {
final SwaggerRoot spec = SwaggerRoot.fromJson(fileInfo.parsedData);
final sourceAbsolutePath = fileInfo.absolutePath;
final namespace = spec.tags.map((tag) => tag.name).firstWhere(
(name) => name.isNotEmpty,
orElse: () => throw Exception(
'Invalid namespace in file "$sourceAbsolutePath"',
),
);
if (namespace.isEmpty) {
throw Exception(
'Invalid namespace "$namespace" in file "$sourceAbsolutePath');
}
final List<String> currentCodeBuffer = [];
final Set<String> importStatementSet = {};
final Set<String> globalStatementSet = {};
for (final endpointEntry in spec.paths.entries) {
final String endpointPath = endpointEntry.key;
final methodMap = endpointEntry.value.requests;
for (final method in ['get', 'post']) {
if (methodMap[method] == null) {
continue;
}
if (methodMap['post']!.operationId.isEmpty) {
throw Exception(
'Invalid operationId in path ${endpointPath}',
);
}
final operation = methodMap[method]!;
final operationId = operation.operationId;
final bool isEndpointDeprecated = operation.deprecated;
final List<SwaggerRequestParameter> parameterList =
operation.parameters;
final String upperCasedMethod = method.toUpperCase();
final String endpointInfo = '`$upperCasedMethod $endpointPath`';
final dartDocCommentForType = assembleDartDocComment([
endpointInfo,
isEndpointDeprecated ? "@deprecated" : null,
]);
final refs =
extractRefsFromOperation(operation.responses, operation.parameters);
final TBinding responseTypeBinding = TBinding(
name: 'T${operationId}Response',
isBound: true,
value: !operation.responses.containsKey('200')
? 'void'
: refs['response']!,
);
final TBinding queryTypeBinding = TBinding(
name: 'T${operationId}Query',
isBound: parameterList.any((item) => item.inParameter == 'query'),
value: 'operations["$operationId"]["parameters"]["query"]',
);
final TBinding bodyTypeBinding = TBinding(
name: 'T${operationId}Body',
isBound: method == 'post' &&
parameterList.any((item) => item.inParameter == 'body'),
value: refs['parameter'] ?? "Never",
);
final TBinding substitutionTypeBinding = TBinding(
name: 'T${operationId}Substitution',
isBound: parameterList.any((item) => item.inParameter == 'path'),
value: 'operations["$operationId"]["parameters"]["path"]',
);
verifyParameterList(parameterList);
final TBinding sharedHeadersTypeBinding = TBinding(
name: 'TStampHeaders',
isBound: false,
value: '{ "$STAMP_HEADER_FIELD_KEY": String }',
);
final TBinding inputTypeBinding = TBinding(
name: 'T${operationId}Input',
isBound: bodyTypeBinding.isBound ||
queryTypeBinding.isBound ||
substitutionTypeBinding.isBound ||
sharedHeadersTypeBinding.isBound,
value: '{ ${[
if (bodyTypeBinding.isBound) 'body: ${bodyTypeBinding.name}',
if (queryTypeBinding.isBound) 'query: ${queryTypeBinding.name}',
if (substitutionTypeBinding.isBound)
'substitution: ${substitutionTypeBinding.name}',
if (sharedHeadersTypeBinding.isBound)
'headers?: ${sharedHeadersTypeBinding.name}', // Optional, because a hook will be injecting the stamp
].join(', ')} }',
);
currentCodeBuffer.addAll(
[queryTypeBinding, substitutionTypeBinding]
.where((binding) => binding.isBound)
.map((binding) => '''
$dartDocCommentForType
typedef ${binding.name} = ${binding.value};
'''),
);
currentCodeBuffer.addAll(
[responseTypeBinding, inputTypeBinding, bodyTypeBinding]
.where((binding) => binding.isBound)
.map((binding) {
// If the binding is `inputTypeBinding`, generate a class.
if (binding == inputTypeBinding) {
final fields = binding.value
.replaceAll(RegExp(r'[{}]'), '')
.split(',')
.map((field) {
final parts = field.split(':').map((p) => p.trim()).toList();
final type = parts[1];
final name = parts[0];
return ' final $type $name;';
}).join('\n');
final constructorParams = binding.value
.replaceAll(RegExp(r'[{}]'), '')
.split(',')
.map((field) {
final parts = field.split(':').map((p) => p.trim()).toList();
final type = parts[1];
final name = parts[0];
final isOptional = type.endsWith('?');
return isOptional ? 'this.$name,' : 'required this.$name,';
}).join('\n ');
return '''
$dartDocCommentForType
class ${binding.name} {
$fields
${binding.name}({
$constructorParams
});
}
''';
} else {
return '''
$dartDocCommentForType
typedef ${binding.name} = ${binding.value};
''';
}
}),
);
}
}
if (currentCodeBuffer.length == 0) {
// Nothing to generate for the current file
return;
}
currentCodeBuffer.insert(
0,
'''
$COMMENT_HEADER
${importStatementSet.where((statement) => statement.isNotEmpty).join('\n')}
import 'public_api.swagger.dart';
${globalStatementSet.where((statement) => statement.isNotEmpty).join('\n')}
''',
);
await safeWriteFileAsync(
"$targetPath/public_api.types.dart", currentCodeBuffer.join("\n\n"));
await formatDocument(targetPath);
}
}