generate method

Library generate()

Implementation

Library generate() {
  lb.body.add(Directive.import('package:http_parser/http_parser.dart'));
  lb.body.add(Directive.import('dart:convert'));
  lb.body.add(Directive.part(partFileName));
  lb.body.add(Directive.part(freezedPartFileName));

  // create class for each schema..
  for (final schemaEntry in api.components!.schemas!.entries) {
    _schemaReference(schemaEntry.key, schemaEntry.value!);
  }

  final fields = [
    MapEntry('dio', _dio),
    MapEntry('baseUri', refer('Uri')),
  ];

  final grouped = api.paths!.entries.groupListsBy((element) =>
      element.value!.operations.values.map((e) => e!.tags ?? []).expand((element) => element).toSet().join());
  final classes = grouped.entries.map((value) => Class((cb) {
        cb
          ..name = '${value.key.titleCase.replaceAll(' ', '')}Api'
          ..constructors.add(Constructor(
            (cb) => cb
              ..requiredParameters.addAll(fields.map((f) => Parameter((pb) => pb
                ..name = f.key
                ..toThis = true))),
          ))
          ..fields.addAll(fields.map((f) => Field((fb) => fb
            ..name = f.key
            ..type = f.value
            ..modifier = FieldModifier.final$)));
        //cb.name = baseName;
        cb.abstract = false;
        for (final path in value.value) {
          for (final operation in path.value!.operations.entries) {
            final pathName = path.key
                .replaceAll(
                    // language=RegExp
                    RegExp(r'[{}]'),
                    '')
                .camelCase;
            final operationName = operation.value!.id == null
                ? '$pathName'
                    '${operation.key.pascalCase}'
                : operation.value!.id!.camelCase;

            final responseClass = ClassBuilder();
            responseClass
              ..name = '${operationName.pascalCase}Response'
              ..abstract = true
              ..constructors.add(Constructor());
            Reference? successResponseBodyType;
            for (final response in operation.value!.responses!.entries) {
              final clientResponseParseParams = <Expression>[];
              final content = (response.value!.content ?? {})[mediaTypeJson.contentType];
//              OpenApiContentType responseContentType;
              Reference? bodyType;
              if (content != null) {
                if (content.schema!.type == APIType.array) {
                  bodyType = _referType('List', generics: [
                    _schemaReference(content.schema!.items!.referenceURI!.pathSegments.last, content.schema!)
                  ]);
                } else {
                  bodyType = _schemaReference(responseClass.name!, content.schema!);
                }

                clientResponseParseParams.add(bodyType
                    .newInstanceNamed('fromJson', [refer('response').property('responseBodyJson')([]).awaited]));
              } else {
                if (response.value!.content?.length == 1) {
                  final responseContent = response.value!.content!.entries.first;
                  bodyType = _toDartType('${refer(responseClass.name!)}Content', responseContent.value!.schema!);
                  if (_typeString == bodyType) {
                    clientResponseParseParams.add(refer('response').property('responseBodyString')([]).awaited);
                  } else if (_dioResponseBody == bodyType) {
                    clientResponseParseParams.add(refer('response').property('responseBodyBytes')([]).awaited);
                  } else {
                    throw StateError('Unsupported bodyType $bodyType for responses.');
                  }
                }
              }
              if (response.key.startsWith('2') || (response.key == 'default' && successResponseBodyType == null)) {
                successResponseBodyType = bodyType;
              }
            }

            final generics = [_dioResponse.addGenerics(successResponseBodyType ?? _void)];

            //lb.body.add(responseClass.build());
            final clientMethod = MethodBuilder()
              ..name = operationName
              ..addDartDoc(operation.value!.summary)
              ..addDartDoc(operation.value!.description)
              ..docs.add('/// ${operation.key}: ${path.key}')
              ..docs.add('///')
              ..returns = _referType('Future', generics: generics)
              ..modifier = MethodModifier.async;
            final clientCode = <Code>[];

            Method((mb) {
              mb
                ..name = operationName
                ..addDartDoc(operation.value!.summary)
                ..addDartDoc(operation.value!.description)
                ..docs.add('/// ${operation.key}: ${path.key}')
                ..returns = _referType('Future', generics: generics);

              final routerParams = <Expression>[];
              final routerParamsNamed = <String, Expression>{};

              if (apiMethodsWithRequest) {
                mb.requiredParameters.add(Parameter((pb) => pb..name = 'request'));
                routerParams.add(refer('request'));
              }

              // ignore: avoid_function_literals_in_foreach_calls
              final allParameters = [...?path.value!.parameters, ...?operation.value!.parameters];
              for (final param in allParameters) {
                final paramType = _toDartType(operationName, param!.schema!);
                final paramNameCamelCase = param.name!.camelCase;
                if (param.description != null) {
                  clientMethod.docs.add('/// * [$paramNameCamelCase]: ${param.description}');
                }
                final p = Parameter((pb) => pb
                  ..name = paramNameCamelCase
                  ..type = paramType.asNullable(!param.isRequired)
                  ..asRequired(this, param.isRequired)
                  ..named = true);
                mb.optionalParameters.add(p);
                clientMethod.optionalParameters.add(p);
                final decodeParameterFrom = (APIParameter param, Expression expression) {
                  final schemaType = ArgumentError.checkNotNull(param.schema?.type, 'param.schema.type');
                  switch (schemaType) {
                    case APIType.string:
                      final Expression? asString = refer('paramToString')([expression]);
                      if (param.schema!.format == 'uuid') {
                        assert(paramType == _apiUuid);
                        return _apiUuid.newInstanceNamed('parse', [asString!]);
                      } else if (paramType != _typeString) {
                        //throw StateError('Unsupported paramType for string $paramType');
                        return _apiUuid.newInstanceNamed('parse', [asString!]);
                      }
                      return asString;
                    case APIType.number:
                      break;
                    case APIType.integer:
                      return refer('paramToInt')([expression]);
                    case APIType.boolean:
                      return refer('paramToBool')([expression]);
                    case APIType.array:
                      //checkState(param.schema!.items!.type == APIType.string);
                      if (param.schema!.items!.enumerated != null && param.schema!.items!.enumerated!.isNotEmpty) {
                        final paramEnumType = (paramType as TypeReference).types.first;
                        return expression
                            .property('map')([
                              Method(
                                (mb) => mb
                                  ..lambda = true
                                  ..requiredParameters.add(Parameter((pb) => pb..name = 'e'))
                                  ..body =
                                      refer(paramEnumType.symbol! + 'Ext').property('fromName')([refer('e')]).code,
                              ).closure
                            ])
                            .property('toList')([]);
                      } else {
                        final paramEnumType = (paramType as TypeReference).types.first;
                        return refer('paramToInt')([expression]);
                        return expression
                            .property('map')([
                              Method(
                                (mb) => mb
                                  ..lambda = true
                                  ..requiredParameters.add(Parameter((pb) => pb..name = 'e'))
                                  ..body = refer('e').code,
                              ).closure
                            ])
                            .property('toList')([]);
                      }
                      return expression;
                    case APIType.object:
                      return expression;
                    default:
                      throw StateError('Invalid schema type $schemaType');
                  }
                };
                final decodeParameter = (Expression? expression) {
                  return refer(param.isRequired ? 'paramRequired' : 'paramOpt')([], {
                    'name': literalString(param.name!),
                    'value': expression!,
                    'decode': Method((mb) {
                      final paramFrom = decodeParameterFrom(param, refer('value'));
                      mb
                        ..lambda = true
                        ..requiredParameters.add(Parameter((pb) => pb..name = 'value'))
                        ..body = paramFrom?.code;
                    }).closure,
                  });
                };
                final paramLocation = ArgumentError.checkNotNull(param.location);
                final paramName = ArgumentError.checkNotNull(param.name);
                routerParamsNamed[paramNameCamelCase] = decodeParameter(_readFromRequest(paramLocation, paramName));
              }

              final body = operation.value!.requestBody;
              if (body != null && body.content!.isNotEmpty) {
                final entry = body.content!.entries.first;

                if (body.content!.length > 1) {
                  _logger.warning('Right now we only support one request body, '
                      'but found: ${body.content!.keys}, only using $entry');
                }

                Map.fromEntries([entry]).forEach((key, reqBody) {
                  final contentType = OpenApiContentType.parse(key);
//                final ct = OpenApiContentType.allKnown
//                    .firstWhere((element) => element.matches(contentType));
                  _createRequestBody(
                    contentType,
                    reqBody!,
                    body.isRequired,
                    operationName,
                    mb,
                    routerParams,
                    clientMethod,
                    clientCode,
                  );
                });
              }
            });

            clientCode.add(Code('final queryParams = <String, dynamic>{};'));

            final allParameters = [
              ...?operation.value?.parameters?.where((element) => element?.location == APIParameterLocation.query),
              ...?path.value?.parameters?.where((element) => element?.location == APIParameterLocation.query),
            ];
            for (final element in allParameters) {
              if (element?.schema?.type == APIType.array) {
                clientCode.add(Code(
                    '''if (${element!.name!.camelCase} != null) queryParams['${element.name}'] = ${element.name!.camelCase}.join(',');'''));
              } else if (element?.schema?.enumerated != null) {
                clientCode.add(Code(
                    '''if (${element!.name!.camelCase} != null) queryParams['${element.name}'] = ${element.name!.camelCase}.name;'''));
              } else {
                clientCode.add(Code(
                    '''if (${element!.name!.camelCase} != null) queryParams['${element.name}'] = ${element.name!.camelCase}.toString();'''));
              }
            }

            clientCode.add(Code('''final uri = baseUri.replace(
      queryParameters: queryParams, path: baseUri.path + '${path.key.replaceAll('{', '\${')}');'''));

            if (successResponseBodyType == _dioResponseBody) {
              clientCode.add(
                Code(
                    'final response = await dio.${operation.key}Uri<${successResponseBodyType!.symbol}>(uri${operation.value?.requestBody != null ? ', data: body' : ''}, options: Options(responseType: ResponseType.stream));'),
              );
            } else if (successResponseBodyType?.symbol == 'List') {
              clientCode.add(
                Code(
                    'final response = await dio.${operation.key}Uri<${successResponseBodyType != null ? 'List<dynamic>' : 'void'}>(uri${operation.value?.requestBody != null ? ', data: body' : ''});'),
              );
            } else if (operation.value!.requestBody?.content?.keys.firstOrNull == 'multipart/form-data') {
              final body = '''FormData.fromMap(<String, dynamic>{
              ${operation.value!.requestBody!.content!.values.first!.schema!.properties!.entries.map((element) => '\'${element.key}\': ${element.value!.type == APIType.object ? 'MultipartFile.fromString(jsonEncode(' + element.key + '.toJson()), filename: \'' + element.key + '.json\', contentType: MediaType(\'application\', \'json\'))' : element.key}').join(',')}
    })''';
              clientCode.add(
                Code(
                    'final response = await dio.${operation.key}Uri<${successResponseBodyType != null ? 'Map<String, dynamic>' : 'void'}>(uri${operation.value?.requestBody != null ? ', data: $body' : ''});'),
              );
            } else {
              // TODO list all primitives?
              if (successResponseBodyType == _typeString) {
                clientCode.add(
                  Code(
                      'final response = await dio.${operation.key}Uri<${successResponseBodyType!.symbol}>(uri${operation.value?.requestBody != null ? ', data: body' : ''});'),
                );
              } else {
                clientCode.add(
                  Code(
                      'final response = await dio.${operation.key}Uri<${successResponseBodyType != null ? 'Map<String, dynamic>' : 'void'}>(uri${operation.value?.requestBody != null ? ', data: body' : ''});'),
                );
              }
            }
            if (successResponseBodyType == null || successResponseBodyType == _dioResponseBody) {
              clientCode.add(Code('return response;'));
            } else if (successResponseBodyType.symbol == 'List') {
              final listType = (successResponseBodyType as TypeReference).types.last;
              clientCode.add(
                Code(
                    '''final parsed = response.data!.map((dynamic e) => ${listType.symbol}.fromJson(e as Map<String, dynamic>)).toList();
                return Response<${successResponseBodyType.symbol}<${listType.symbol}>>(
                  data: parsed,
                  headers: response.headers,
                  requestOptions: response.requestOptions,
                  isRedirect: response.isRedirect,
                  statusCode: response.statusCode,
                  redirects: response.redirects,
                  extra: response.extra,
                );
                '''),
              );
            } else {
              clientCode.add(
                Code(
                    '''final parsed = ${successResponseBodyType == _typeString ? 'response.data!' : '${successResponseBodyType.symbol}.fromJson(response.data!)'};
                return Response<${successResponseBodyType.symbol}>(
                  data: parsed,
                  headers: response.headers,
                  requestOptions: response.requestOptions,
                  isRedirect: response.isRedirect,
                  statusCode: response.statusCode,
                  redirects: response.redirects,
                  extra: response.extra,
                );
                '''),
              );
            }

            clientMethod.body = Block.of(clientCode);
            cb.methods.add(clientMethod.build());
          }
        }
      }));
  for (final element in classes) {
    lb.body.add(element);
  }

  return lb.build();
}