generateMappedSwaggerTypes function

Future<void> generateMappedSwaggerTypes({
  1. required List<TFileInfo> fileList,
  2. required String targetPath,
})

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);
  }
}