From aa9c58eab7f7427f588fc4e290821758a690311a Mon Sep 17 00:00:00 2001 From: Hadrien Lejard Date: Sat, 10 Apr 2021 13:18:53 +0200 Subject: [PATCH] Generator - fix headers and required parameters (#239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Null safety (non stable version) (#219) * GZip note * Update faq.md * CR fixes * Moved to the faq.md * HTTP Auth example * CR fix * CR fix * Base url note Runtime base url note update Update faq.md * Packages upgraded (#142) * Release 3.0.3 * add develop branch to travis * fix travis badge * Make code quality improvements in generator.dart (#147) * Add optionalBody parameter to suppress warnings (#151) * Add optionalBody parameter to suppress warnings Fixes #149 * Improve formatting * Remove redundant _methodWithBody function Use a single source of truth (the method's optionalBody property) * Show optional body hint in warning message * Make optionalBody configurable for all methods Explicitly set the default value for every method * Specify type for optionalBody argument Co-authored-by: Wilko Manger * Fix typo and invalid code sample in documentation (#156) * Preserve uri current query params in buildUri (#166) * This allows chopper API methods with generic attributes (#170) @Post(path: URL.DOCUMENT_PRIOR_INFO) Future> getPriorInfo(@Body() Params params); Which is usefull when single API method may respond differently depending on request parameter values. Co-authored-by: Aleksandr Malkov * Make conversion helpers can be `async` (#175) #174 - locally tested * Dependency update (#178) Version bumped Removed Angular / Web examples With nullability added Update generator.dart Dependency cleanup Update pubspec.yaml Update pubspec.yaml * Re-add Angular example and fix travis (#181) Re add example removed here (https://github.com/lejard-h/chopper/pull/178/files) Upgrade to Angular 6, but I had to remove jaguar_serializer which is not maintain anymore (last update 1 year ago). Then use new Dart command to run pub, analyzer and formatter * Publish package from travis (#182) * fix * fix scopes * allow_failure * comment override * Add suport for JsonApi utf8 serialization (#185) * Add suport for JsonApi utf8 serialization * Move json api header into a constant * The Big Public API Documentation update, volume 1. (#189) * Update converters.md with wording and formatting fixes * Update the documentation of classes and functions in interceptor.dart * Update the documentation of classes and functions in base.dart * Update the documentation of public functions in utils.dart * Update the documentation of public classes and functions in request.dart * Update the documentation of public classes and functions in response.dart * Update the documentation of ConvertRequest, ConvertResponse, and FactoryConverter in annotations.dart * Fix typos in the documentation in annotations.dart * Remove TODO from ChopperService.definitionType's documentation * Add example usage to FactoryConverter's documentation * Fix accidental typo in ChopperService's class name * The Big Public API Documentation update, volume 2. (#193) * Update the documentation of BuiltValueConverter * Update the documentation and usage guide of BuiltValueConverter * Update description texts in pubspec.yaml files * Change homepage tag to documentation in chopper/pubspec.yaml * Change homepage tag to documentation in chopper_built_value/pubspec.yaml and make it point to its actual documentation page * Change homepage tag to documentation in chopper_generator/pubspec.yaml * Change homepage tag to documentation in chopper/example/pubspec.yaml and make it point to the actual documentation * Fix wording in chopper_generator/README.md * Fix wording in chopper_built_value/README.md * Fix HTML escape sequences escaping into markdown and inline docs; Fix code sample syntax errors in some docs Those pesky escapists. * Add dynamic version tag to built-value-converter.md * Fix typo in the package name in the installation section of built-value-converter.md * Fix typo in built-value-converter.md * Reorganize and update chopper/README.md * The Big Public API Documentation Update, volume 3. (#196) * Rework and update getting-started.md * Reword parts of the API docs in annotations.dart * Add description of path resolution behavior in requests.md This commit addresses issue #195 * Reword parts of the documentations in requests.md * Reword the error handling description in getting-started.md * Reword and update most of the documentation in requests.md * Add missing @s to annotation mentions in getting-started.md * Remove an unnecessary new line from getting-started.md * Add further explanation to an example service's create method in getting-started.md * Update the README of chopper_built_value * Update the README of chopper_generator * Update and reword top level README.md and chopper/README.md * Try to fix text formatting in requests.md * Fix wording requests.md's form URL encoded section * Remove unsafe hint on build_runner from getting-started.md * Implement authenticator (#198) * Implement authenticator * Changes for PR Co-authored-by: Ivan Terekhin * Add Flutter Favorite badge (#206) * feature/updated doc (#214) * updated doc * fixed typo Co-authored-by: dafinrs * Feature/support body get (#201) * Feature/null safety migration (#212) * Pubspec for ChopperBuiltValue * Begin resolving null safety dependencies * Initial change * Implement null safety on chopper_generator * Fix comments on PR * Fixed some comments * version updates * fixed issues with versions * fix comments on chopperClient nullability * fixed tests * Fixed issues with nullable client * Bumped build version to 2.0.0 * updated libs * made parts not nullable * Update chopper/README.md Co-authored-by: Ivan Terekhin * Update chopper/README.md Co-authored-by: Ivan Terekhin * Update chopper_generator/pubspec.yaml Co-authored-by: Ivan Terekhin * Fix for null safety in applyHeaders * More deps upgrade Co-authored-by: Ivan Terekhin Co-authored-by: István Juhos Co-authored-by: Uladzimir Paliukhovich Co-authored-by: Uladzimir_Paliukhovich Co-authored-by: uladzimir_paliukhovich <> * Add double quotes to Curl print String (#218) Prints the Curl URL wrapped with double-quotes. It fixes issues when pasting the resulting log to the command line, where the curl request in some cases would not be closed by the terminal or fail to execute. * Enable GitHub action (#216) * github acions with mono_repo * fail test * cleanup * badge * regenerate * regenerate * format * fix analyzer * fix analyzer * fix test * regenerate * cleanup badge * Naming fix * remove dev chanel * ignore vscode * revert readme change * remove travis * format * fix * fix * format * fix tests Co-authored-by: Ivan Terekhin * Add an OPTIONS request type (#215) * Add an OPTIONS request type * Null safety fix Co-authored-by: Ivan Terekhin * changelog * docs: add lejard-h as a contributor (#221) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * docs: add stewemetal as a contributor (#222) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Hadrien Lejard * docs: add JEuler as a contributor (#223) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Hadrien Lejard * docs: add fryette as a contributor (#224) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Hadrien Lejard * docs: add Vovanella95 as a contributor (#225) * docs: update README.md [skip ci] * docs: create .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: Hadrien Lejard * update Changelog for 4.0.0 release (#220) * update Changelog * contributors * cleanup * changelog Co-authored-by: Ivan Terekhin Co-authored-by: Juhos István Co-authored-by: Louis Matthijssen Co-authored-by: Wilko Manger Co-authored-by: Tamas Balogh Co-authored-by: Mohammad Omidvar Tehrani Co-authored-by: cpthooch Co-authored-by: Aleksandr Malkov Co-authored-by: Study-Log <58581613+Study-Log@users.noreply.github.com> Co-authored-by: Daniel Gomez Co-authored-by: Graham Smith Co-authored-by: Eugeny Sampir Co-authored-by: dafi Co-authored-by: dafinrs Co-authored-by: Alfredo Bautista <71638694+alfredo-handcash@users.noreply.github.com> Co-authored-by: Uladzimir Paliukhovich Co-authored-by: Uladzimir_Paliukhovich Co-authored-by: Alex Queudot Co-authored-by: Shane Farmer Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> * fix params and headers * fix Co-authored-by: Ivan Terekhin Co-authored-by: Juhos István Co-authored-by: Louis Matthijssen Co-authored-by: Wilko Manger Co-authored-by: Tamas Balogh Co-authored-by: Mohammad Omidvar Tehrani Co-authored-by: cpthooch Co-authored-by: Aleksandr Malkov Co-authored-by: Study-Log <58581613+Study-Log@users.noreply.github.com> Co-authored-by: Daniel Gomez Co-authored-by: Graham Smith Co-authored-by: Eugeny Sampir Co-authored-by: dafi Co-authored-by: dafinrs Co-authored-by: Alfredo Bautista <71638694+alfredo-handcash@users.noreply.github.com> Co-authored-by: Uladzimir Paliukhovich Co-authored-by: Uladzimir_Paliukhovich Co-authored-by: Alex Queudot Co-authored-by: Shane Farmer Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- chopper/example/definition.chopper.dart | 5 +- chopper/test/base_test.dart | 19 ++-- chopper/test/converter_test.dart | 2 +- chopper/test/interceptors_test.dart | 30 ++++-- chopper/test/test_service.chopper.dart | 17 ++- chopper/test/test_service.dart | 2 +- chopper_generator/lib/src/generator.dart | 129 +++++++++++++++-------- 7 files changed, 142 insertions(+), 62 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index dce9ae9c..0a26d4a8 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -27,7 +27,10 @@ class _$MyService extends MyService { Future>> getMapResource(String id) { final $url = '/resources/'; final $params = {'id': id}; - final $headers = {'foo': 'bar'}; + final $headers = { + 'foo': 'bar', + }; + final $request = Request('GET', $url, client.baseUrl, parameters: $params, headers: $headers); return client.send, Map>($request); diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 1d0571c3..d487184d 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -64,7 +64,7 @@ void main() { final chopper = buildClient(httpClient); final service = chopper.getService(); - final response = await service.getTest('1234'); + final response = await service.getTest('1234', dynamicHeader: ''); expect(response.body, equals('get response')); expect(response.statusCode, equals(200)); @@ -392,7 +392,7 @@ void main() { final service = HttpTestService.create(chopper); - await service.getTest('1234'); + await service.getTest('1234', dynamicHeader: ''); client.close(); }); @@ -613,7 +613,7 @@ void main() { }); final service = HttpTestService.create(chopper); - await service.getTest('1234'); + await service.getTest('1234', dynamicHeader: ''); client.close(); chopper.dispose(); @@ -632,7 +632,7 @@ void main() { }); final service = HttpTestService.create(chopper); - await service.getTest('1234'); + await service.getTest('1234', dynamicHeader: ''); client.close(); chopper.dispose(); @@ -646,7 +646,7 @@ void main() { final chopper = buildClient(client); final service = HttpTestService.create(chopper); - final res = await service.getTest('1234'); + final res = await service.getTest('1234', dynamicHeader: ''); expect(res.isSuccessful, isFalse); expect(res.statusCode, equals(400)); @@ -665,7 +665,7 @@ void main() { final chopper = buildClient(client, JsonConverter()); final service = HttpTestService.create(chopper); - final res = await service.getTest('1234'); + final res = await service.getTest('1234', dynamicHeader: ''); expect(res.isSuccessful, isFalse); expect(res.statusCode, equals(400)); @@ -719,7 +719,12 @@ void main() { final service = chopper.getService(); try { - await service.getTest('1234').timeout(const Duration(seconds: 3)); + await service + .getTest( + '1234', + dynamicHeader: '', + ) + .timeout(const Duration(seconds: 3)); } catch (e) { expect(e is TimeoutException, isTrue); } diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index c0e27e9b..1941445b 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -50,7 +50,7 @@ void main() { final service = HttpTestService.create(chopper); try { - await service.getTest('1'); + await service.getTest('1', dynamicHeader: ''); } catch (e) { expect(e is Response, isTrue); expect((e as Response).body is _ConvertedError, isTrue); diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index ef8d96b7..a325faeb 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -36,7 +36,10 @@ void main() { client: requestClient, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); }); test('RequestInterceptorFunc', () async { @@ -51,7 +54,10 @@ void main() { client: requestClient, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); }); test('ResponseInterceptor', () async { @@ -63,7 +69,10 @@ void main() { client: responseClient, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); expect(ResponseIntercept.intercepted, isA<_Intercepted>()); }); @@ -84,7 +93,10 @@ void main() { client: responseClient, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); expect(intercepted, isA<_Intercepted>()); }); @@ -105,7 +117,10 @@ void main() { client: responseClient, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); expect(intercepted, isA<_Intercepted>()); }); @@ -155,7 +170,10 @@ void main() { client: client, ); - await chopper.getService().getTest('1234'); + await chopper.getService().getTest( + '1234', + dynamicHeader: '', + ); }); final fakeRequest = Request( diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index f4bbc473..2d03d152 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -17,9 +17,12 @@ class _$HttpTestService extends HttpTestService { final definitionType = HttpTestService; @override - Future> getTest(String id, {String dynamicHeader = ''}) { + Future> getTest(String id, {required String dynamicHeader}) { final $url = '/test/get/$id'; - final $headers = {'test': dynamicHeader}; + final $headers = { + 'test': dynamicHeader, + }; + final $request = Request('GET', $url, client.baseUrl, headers: $headers); return client.send($request); } @@ -125,7 +128,10 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { final $url = '/test/delete/$id'; - final $headers = {'foo': 'bar'}; + final $headers = { + 'foo': 'bar', + }; + final $request = Request('DELETE', $url, client.baseUrl, headers: $headers); return client.send($request); } @@ -158,7 +164,10 @@ class _$HttpTestService extends HttpTestService { @override Future> postFormUsingHeaders(Map fields) { final $url = '/test/form/body'; - final $headers = {'content-type': 'application/x-www-form-urlencoded'}; + final $headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + final $body = fields; final $request = Request('POST', $url, client.baseUrl, body: $body, headers: $headers); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 1fcd9940..426b045d 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -15,7 +15,7 @@ abstract class HttpTestService extends ChopperService { @Get(path: 'get/{id}') Future> getTest( @Path() String id, { - @Header('test') String dynamicHeader = '', + @Header('test') required String dynamicHeader, }); @Head(path: 'head') diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 1ce46a94..185f8124 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -132,42 +132,34 @@ class ChopperGenerator extends GeneratorForAnnotation { return Method((b) { b.annotations.add(refer('override')); b.name = m.displayName; - b.returns = - Reference(m.returnType.getDisplayString(withNullability: false)); - b.types.addAll(m.typeParameters - .map((t) => Reference(t.getDisplayString(withNullability: false)))); - b.requiredParameters.addAll(m.parameters - .where((p) => p.isNotOptional) - .map((p) => Parameter((pb) => pb - ..name = p.name - ..type = Reference( - p.type.getDisplayString(withNullability: p.type.isNullable))))); - - b.optionalParameters.addAll(m.parameters - .where((p) => p.isOptionalPositional) - .map((p) => Parameter((pb) { - pb - ..name = p.name - ..type = Reference( - p.type.getDisplayString(withNullability: false)); - - if (p.defaultValueCode != null) { - pb.defaultTo = Code(p.defaultValueCode); - } - }))); + + // We don't support returning null Type + b.returns = Reference( + m.returnType.getDisplayString(withNullability: false), + ); + + // And null Typed parameters + b.types.addAll( + m.typeParameters.map( + (t) => Reference(t.getDisplayString(withNullability: false)), + ), + ); + + b.requiredParameters.addAll( + m.parameters + .where((p) => p.isRequiredPositional) + .map(buildRequiredPositionalParam), + ); b.optionalParameters.addAll( - m.parameters.where((p) => p.isNamed).map((p) => Parameter((pb) { - pb - ..named = true - ..name = p.name - ..type = Reference(p.type - .getDisplayString(withNullability: p.type.isNullable)); - - if (p.defaultValueCode != null) { - pb.defaultTo = Code(p.defaultValueCode); - } - }))); + m.parameters + .where((p) => p.isOptionalPositional) + .map(buildOptionalPositionalParam), + ); + + b.optionalParameters.addAll( + m.parameters.where((p) => p.isNamed).map(buildNamedParam), + ); final blocks = [ url.assignFinal(_urlVar).statement, @@ -472,14 +464,20 @@ class ChopperGenerator extends GeneratorForAnnotation { } Code? _generateHeaders(MethodElement methodElement, ConstantReader method) { - final headers = {}; + final codeBuffer = StringBuffer('')..writeln('{'); + // Search for @Header anotation in method parameters final annotations = _getAnnotations(methodElement, chopper.Header); annotations.forEach((parameter, ConstantReader annotation) { - final name = - annotation.peek('name')?.stringValue ?? parameter.displayName; - headers[literal(name)] = refer(parameter.displayName); + final paramName = parameter.displayName; + final name = annotation.peek('name')?.stringValue ?? paramName; + + if (parameter.type.isNullable) { + codeBuffer.writeln('if ($paramName != null) \'$name\': $paramName,'); + } else { + codeBuffer.writeln('\'$name\': $paramName,'); + } }); final headersReader = method.peek('headers'); @@ -489,16 +487,19 @@ class ChopperGenerator extends GeneratorForAnnotation { methodAnnotations.forEach((headerName, headerValue) { if (headerName != null && headerValue != null) { - headers[literal(headerName.toStringValue())] = - literal(headerValue.toStringValue()); + codeBuffer.writeln( + '\'${headerName.toStringValue()}\': ${literal(headerValue.toStringValue())},', + ); } }); - if (headers.isEmpty) { + codeBuffer.writeln('};'); + final code = codeBuffer.toString(); + if (code == '{\n};\n') { return null; } - return literalMap(headers).assignFinal(_headersVar).statement; + return Code('final $_headersVar = $code'); } } @@ -519,3 +520,47 @@ String getMethodName(ConstantReader method) => extension DartTypeExtension on DartType { bool get isNullable => nullabilitySuffix != NullabilitySuffix.none; } + +// All positional required params must support nullability +Parameter buildRequiredPositionalParam(ParameterElement p) { + return Parameter( + (pb) => pb + ..name = p.name + ..type = Reference( + p.type.getDisplayString(withNullability: p.type.isNullable), + ), + ); +} + +// All optional positional params must support nullability +Parameter buildOptionalPositionalParam(ParameterElement p) { + return Parameter((pb) { + pb + ..name = p.name + ..type = Reference( + p.type.getDisplayString(withNullability: p.type.isNullable), + ); + + if (p.defaultValueCode != null) { + pb.defaultTo = Code(p.defaultValueCode); + } + }); +} + +// Named params can be optional or required, they also need to support +// nullability +Parameter buildNamedParam(ParameterElement p) { + return Parameter((pb) { + pb + ..named = true + ..name = p.name + ..required = p.isRequiredNamed + ..type = Reference( + p.type.getDisplayString(withNullability: p.type.isNullable), + ); + + if (p.defaultValueCode != null) { + pb.defaultTo = Code(p.defaultValueCode); + } + }); +}