From 656208338c723416172de865ecf91fc856fb84e3 Mon Sep 17 00:00:00 2001 From: seunggi Date: Tue, 5 Mar 2024 17:10:50 +0900 Subject: [PATCH] Support dart retrofit 4.1.0 @Extras parameter option (#209) --- swagger_parser/CHANGELOG.md | 4 + swagger_parser/README.md | 5 + swagger_parser/example/swagger_parser.yaml | 5 + swagger_parser/lib/src/config/swp_config.dart | 20 ++ .../generator/config/generator_config.dart | 9 + .../generator/generator/fill_controller.dart | 1 + .../generator/model/programming_language.dart | 2 + .../dart_retrofit_client_template.dart | 12 +- .../parser/model/universal_request_type.dart | 3 + swagger_parser/pubspec.yaml | 2 +- .../test/generator/rest_clients_test.dart | 226 +++++++++++++++++- 11 files changed, 285 insertions(+), 4 deletions(-) diff --git a/swagger_parser/CHANGELOG.md b/swagger_parser/CHANGELOG.md index 8e34be3e..74d62e57 100644 --- a/swagger_parser/CHANGELOG.md +++ b/swagger_parser/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.17.0 + +- Add new config parameter `extras_parameter_by_default` from [retrofit 4.1.0](https://pub.dev/packages/retrofit/changelog#410) for ([#208][https://github.com/Carapacik/swagger_parser/issues/208]) + ## 1.16.4 - Fixed errors with `required_by_default` diff --git a/swagger_parser/README.md b/swagger_parser/README.md index 4110f54d..ca6a2451 100644 --- a/swagger_parser/README.md +++ b/swagger_parser/README.md @@ -79,6 +79,11 @@ swagger_parser: # Optional. Set default content-type for all requests. default_content_type: "application/json" + # Optional (dart only). + # Support @Extras annotation for interceptors. + # If the value is 'true', then the annotation will be added to all requests. + extras_parameter_by_default: false + # Optional (dart only). # It is used if the value does not have the annotations 'required' and 'nullable'. # If the value is 'true', then value be 'required', if the value is 'false', then 'nullable'. diff --git a/swagger_parser/example/swagger_parser.yaml b/swagger_parser/example/swagger_parser.yaml index 06c728ed..2106c73b 100644 --- a/swagger_parser/example/swagger_parser.yaml +++ b/swagger_parser/example/swagger_parser.yaml @@ -22,6 +22,11 @@ swagger_parser: # Optional. Set default content-type for all requests. default_content_type: "application/json" + # Optional (dart only). + # Support @Extras annotation for interceptors. + # If the value is 'true', then the annotation will be added to all requests. + extras_parameter_by_default: false + # Optional (dart only). # It is used if the value does not have the annotations 'required' and 'nullable'. # If the value is 'true', then value be 'required', if the value is 'false', then 'nullable'. diff --git a/swagger_parser/lib/src/config/swp_config.dart b/swagger_parser/lib/src/config/swp_config.dart index 7374dfc4..20bdc5e7 100644 --- a/swagger_parser/lib/src/config/swp_config.dart +++ b/swagger_parser/lib/src/config/swp_config.dart @@ -27,6 +27,7 @@ class SWPConfig { this.originalHttpResponse = false, this.replacementRules = const [], this.defaultContentType = 'application/json', + this.extrasParameterByDefault = false, this.pathMethodName = false, this.requiredByDefault = true, this.mergeClients = false, @@ -53,6 +54,7 @@ class SWPConfig { required this.originalHttpResponse, required this.replacementRules, required this.defaultContentType, + required this.extrasParameterByDefault, required this.pathMethodName, required this.requiredByDefault, required this.mergeClients, @@ -117,6 +119,13 @@ class SWPConfig { ); } + final extrasParameterByDefault = yamlMap['extras_parameter_by_default']; + if (extrasParameterByDefault is! bool?) { + throw const ConfigException( + "Config parameter 'extras_parameter_by_default' must be bool.", + ); + } + final pathMethodName = yamlMap['path_method_name']; if (pathMethodName is! bool?) { throw const ConfigException( @@ -276,6 +285,8 @@ class SWPConfig { name: name, pathMethodName: pathMethodName ?? dc.pathMethodName, defaultContentType: defaultContentType ?? dc.defaultContentType, + extrasParameterByDefault: + extrasParameterByDefault ?? dc.extrasParameterByDefault, requiredByDefault: requiredByDefault ?? dc.requiredByDefault, mergeClients: mergeClients ?? dc.mergeClients, enumsParentPrefix: enumsParentPrefix ?? dc.enumsParentPrefix, @@ -362,6 +373,14 @@ class SWPConfig { /// @Headers({'Content-Type': 'PARSED CONTENT TYPE'}) final String defaultContentType; + /// DART ONLY + /// Add extra parameter to all requests. Supported after retrofit 4.1.0. + /// + /// + /// @POST('/path/') + /// Future myMethod({@Extras() Map? extras}); + final bool extrasParameterByDefault; + /// DART ONLY /// It is used if the value does not have the annotations `required` and `nullable`. /// If the value is `true`, then value be `required`. @@ -389,6 +408,7 @@ class SWPConfig { language: language, jsonSerializer: jsonSerializer, defaultContentType: defaultContentType, + extrasParameterByDefault: extrasParameterByDefault, rootClient: rootClient, rootClientName: rootClientName, clientPostfix: clientPostfix, diff --git a/swagger_parser/lib/src/generator/config/generator_config.dart b/swagger_parser/lib/src/generator/config/generator_config.dart index dbcee845..be66f88e 100644 --- a/swagger_parser/lib/src/generator/config/generator_config.dart +++ b/swagger_parser/lib/src/generator/config/generator_config.dart @@ -12,6 +12,7 @@ class GeneratorConfig { this.jsonSerializer = JsonSerializer.jsonSerializable, this.defaultContentType = 'application/json', this.rootClient = true, + this.extrasParameterByDefault = false, this.rootClientName = 'RestClient', this.clientPostfix, this.exportFile = true, @@ -80,6 +81,14 @@ class GeneratorConfig { /// @Headers({'Content-Type': 'PARSED CONTENT TYPE'}) final String defaultContentType; + /// DART ONLY + /// Add extras parameter to all requests. Supported after retrofit 4.1.0. + /// + /// + /// @POST('/path/') + /// Future myMethod({@Extras() Map? extras}); + final bool extrasParameterByDefault; + /// Optional. Set regex replacement rules for the names of the generated classes/enums. /// All rules are applied in order. final List replacementRules; diff --git a/swagger_parser/lib/src/generator/generator/fill_controller.dart b/swagger_parser/lib/src/generator/generator/fill_controller.dart index 08b4d3db..4ad5393d 100644 --- a/swagger_parser/lib/src/generator/generator/fill_controller.dart +++ b/swagger_parser/lib/src/generator/generator/fill_controller.dart @@ -48,6 +48,7 @@ final class FillController { restClient.name.toPascal + postfix.toPascal, markFilesAsGenerated: config.markFilesAsGenerated, defaultContentType: config.defaultContentType, + extrasParameterByDefault: config.extrasParameterByDefault, originalHttpResponse: config.originalHttpResponse, ), ); diff --git a/swagger_parser/lib/src/generator/model/programming_language.dart b/swagger_parser/lib/src/generator/model/programming_language.dart index 67252e0f..54da3ba7 100644 --- a/swagger_parser/lib/src/generator/model/programming_language.dart +++ b/swagger_parser/lib/src/generator/model/programming_language.dart @@ -109,6 +109,7 @@ enum ProgrammingLanguage { String name, { required bool markFilesAsGenerated, required String defaultContentType, + bool extrasParameterByDefault = false, bool originalHttpResponse = false, }) => switch (this) { @@ -117,6 +118,7 @@ enum ProgrammingLanguage { name: name, markFileAsGenerated: markFilesAsGenerated, defaultContentType: defaultContentType, + extrasParameterByDefault: extrasParameterByDefault, originalHttpResponse: originalHttpResponse, ), kotlin => kotlinRetrofitClientTemplate( diff --git a/swagger_parser/lib/src/generator/templates/dart_retrofit_client_template.dart b/swagger_parser/lib/src/generator/templates/dart_retrofit_client_template.dart index a50e6579..5d4f4254 100644 --- a/swagger_parser/lib/src/generator/templates/dart_retrofit_client_template.dart +++ b/swagger_parser/lib/src/generator/templates/dart_retrofit_client_template.dart @@ -12,6 +12,7 @@ String dartRetrofitClientTemplate({ required String name, required bool markFileAsGenerated, required String defaultContentType, + bool extrasParameterByDefault = false, bool originalHttpResponse = false, }) { final sb = StringBuffer( @@ -32,6 +33,7 @@ abstract class $name { request, defaultContentType, originalHttpResponse: originalHttpResponse, + extrasParameterByDefault: extrasParameterByDefault, ), ); } @@ -43,6 +45,7 @@ String _toClientRequest( UniversalRequest request, String defaultContentType, { required bool originalHttpResponse, + required bool extrasParameterByDefault, }) { final responseType = request.returnType == null ? 'void' @@ -53,7 +56,7 @@ String _toClientRequest( ${descriptionComment(request.description, tabForFirstLine: false, tab: ' ', end: ' ')}${request.isDeprecated ? "@Deprecated('This method is marked as deprecated')\n " : ''}${_contentTypeHeader(request, defaultContentType)}@${request.requestType.name.toUpperCase()}('${request.route}') Future<${originalHttpResponse ? 'HttpResponse<$responseType>' : responseType}> ${request.name}(''', ); - if (request.parameters.isNotEmpty) { + if (request.parameters.isNotEmpty || extrasParameterByDefault) { sb.write('{\n'); } final sortedByRequired = List.from( @@ -62,7 +65,10 @@ String _toClientRequest( for (final parameter in sortedByRequired) { sb.write('${_toParameter(parameter)}\n'); } - if (request.parameters.isNotEmpty) { + if (extrasParameterByDefault) { + sb.write(_addExtraParameter()); + } + if (request.parameters.isNotEmpty || extrasParameterByDefault) { sb.write(' });\n'); } else { sb.write(');\n'); @@ -87,6 +93,8 @@ String _fileImport(UniversalRestClient restClient) => restClient.requests.any( ? "import 'dart:io';\n\n" : ''; +String _addExtraParameter() => ' @Extras() Map? extras,\n'; + String _toParameter(UniversalRequestType parameter) { var parameterType = parameter.type.toSuitableType(ProgrammingLanguage.dart); // https://github.com/trevorwang/retrofit.dart/issues/631 diff --git a/swagger_parser/lib/src/parser/model/universal_request_type.dart b/swagger_parser/lib/src/parser/model/universal_request_type.dart index 2e813d96..6198c383 100644 --- a/swagger_parser/lib/src/parser/model/universal_request_type.dart +++ b/swagger_parser/lib/src/parser/model/universal_request_type.dart @@ -54,6 +54,9 @@ enum HttpParameterType { /// `@Body` body('Body'), + /// `@Extras` + extras('Extras'), + /// `@Query` query('Query'), diff --git a/swagger_parser/pubspec.yaml b/swagger_parser/pubspec.yaml index 518de103..e6f29400 100644 --- a/swagger_parser/pubspec.yaml +++ b/swagger_parser/pubspec.yaml @@ -1,6 +1,6 @@ name: swagger_parser description: Package that generates REST clients and data classes from OpenApi definition file -version: 1.16.4 +version: 1.17.0 repository: https://github.com/Carapacik/swagger_parser/tree/main/swagger_parser homepage: https://omega-r.com topics: diff --git a/swagger_parser/test/generator/rest_clients_test.dart b/swagger_parser/test/generator/rest_clients_test.dart index 1a557150..4422af30 100644 --- a/swagger_parser/test/generator/rest_clients_test.dart +++ b/swagger_parser/test/generator/rest_clients_test.dart @@ -909,7 +909,7 @@ interface ClassNameClient { }); }); - group('All request types of parameter', () { + group('All request types of parameter except extras type', () { test('dart + retrofit', () async { const restClient = UniversalRestClient( name: 'ClassName', @@ -2125,6 +2125,230 @@ interface ClassNameClient { }); }); + group('None parameter with @Extras option for dart', () { + test('dart + retrofit', () async { + const restClient = UniversalRestClient( + name: 'ClassName', + imports: {}, + requests: [ + UniversalRequest( + name: 'getRequest', + requestType: HttpRequestType.get, + route: '/{id}', + returnType: null, + parameters: [], + ), + ], + ); + const fillController = FillController( + config: GeneratorConfig( + name: '', + outputDirectory: '', + extrasParameterByDefault: true, + ), + ); + final filledContent = fillController.fillRestClientContent(restClient); + const expectedContents = ''' +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'class_name_client.g.dart'; + +@RestApi() +abstract class ClassNameClient { + factory ClassNameClient(Dio dio, {String? baseUrl}) = _ClassNameClient; + + @GET('/{id}') + Future getRequest({ + @Extras() Map? extras, + }); +} +'''; + expect(filledContent.content, expectedContents); + }); + + test('kotlin + retrofit', () async { + const restClient = UniversalRestClient( + name: 'ClassName', + imports: {}, + requests: [], + ); + const fillController = FillController( + config: GeneratorConfig( + name: '', + outputDirectory: '', + language: ProgrammingLanguage.kotlin, + extrasParameterByDefault: true, + ), + ); + final filledContent = fillController.fillRestClientContent(restClient); + const expectedContents = ''' +import retrofit2.http.* + +interface ClassNameClient {} +'''; + expect(filledContent.content, expectedContents); + }); + }); + + group('All request types of parameter with @Extras option for dart', () { + test('dart + retrofit', () async { + const restClient = UniversalRestClient( + name: 'ClassName', + imports: {}, + requests: [ + UniversalRequest( + name: 'getRequest', + requestType: HttpRequestType.get, + route: '/{id}', + returnType: null, + parameters: [ + UniversalRequestType( + parameterType: HttpParameterType.header, + type: UniversalType( + type: 'string', + name: 'token', + isRequired: true, + ), + name: 'Authorization', + ), + UniversalRequestType( + parameterType: HttpParameterType.query, + type: UniversalType( + type: 'string', + name: 'alex', + isRequired: true, + ), + name: 'name', + ), + UniversalRequestType( + parameterType: HttpParameterType.path, + type: UniversalType( + type: 'int', + name: 'id', + isRequired: true, + ), + name: 'id', + ), + UniversalRequestType( + parameterType: HttpParameterType.body, + type: UniversalType( + type: 'Another', + name: 'another', + isRequired: true, + ), + ), + ], + ), + ], + ); + const fillController = FillController( + config: GeneratorConfig( + name: '', + outputDirectory: '', + extrasParameterByDefault: true, + ), + ); + final filledContent = fillController.fillRestClientContent(restClient); + const expectedContents = ''' +import 'package:dio/dio.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'class_name_client.g.dart'; + +@RestApi() +abstract class ClassNameClient { + factory ClassNameClient(Dio dio, {String? baseUrl}) = _ClassNameClient; + + @GET('/{id}') + Future getRequest({ + @Header('Authorization') required String token, + @Query('name') required String alex, + @Path('id') required int id, + @Body() required Another another, + @Extras() Map? extras, + }); +} +'''; + expect(filledContent.content, expectedContents); + }); + + test('kotlin + retrofit', () async { + const restClient = UniversalRestClient( + name: 'ClassName', + imports: {}, + requests: [ + UniversalRequest( + name: 'getRequest', + requestType: HttpRequestType.get, + route: '/{id}', + returnType: null, + parameters: [ + UniversalRequestType( + parameterType: HttpParameterType.header, + type: UniversalType( + type: 'string', + name: 'token', + isRequired: true, + ), + name: 'Authorization', + ), + UniversalRequestType( + parameterType: HttpParameterType.query, + type: UniversalType( + type: 'string', + name: 'alex', + isRequired: true, + ), + name: 'name', + ), + UniversalRequestType( + parameterType: HttpParameterType.path, + type: UniversalType( + type: 'int', + name: 'id', + isRequired: true, + ), + name: 'id', + ), + UniversalRequestType( + parameterType: HttpParameterType.body, + type: UniversalType( + type: 'Another', + name: 'another', + isRequired: true, + ), + ), + ], + ), + ], + ); + const fillController = FillController( + config: GeneratorConfig( + name: '', + outputDirectory: '', + language: ProgrammingLanguage.kotlin, + extrasParameterByDefault: true, + ), + ); + final filledContent = fillController.fillRestClientContent(restClient); + const expectedContents = ''' +import retrofit2.http.* + +interface ClassNameClient { + @GET("/{id}") + suspend fun getRequest( + @Header("Authorization") token: String, + @Query("name") alex: String, + @Path("id") id: int, + @Body another: Another, + ) +} +'''; + expect(filledContent.content, expectedContents); + }); + }); + group('Description', () { test('dart + retrofit', () async { const restClient = UniversalRestClient(