From f25fbe4a9e2eab08424f48030d271681a37ca2ba Mon Sep 17 00:00:00 2001 From: Youssef Raafat Date: Tue, 25 May 2021 08:26:19 +0200 Subject: [PATCH 01/61] Fix Header Option Casting (#260) Co-authored-by: Ivan Terekhin --- chopper/lib/src/base.dart | 2 +- chopper_generator/lib/chopper_generator.dart | 2 +- chopper_generator/lib/src/generator.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index d936c669..ff4bb051 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -340,7 +340,7 @@ class ChopperClient { Map headers = const {}, Map parameters = const {}, String? baseUrl, - dynamic? body, + dynamic body, }) => send( Request( diff --git a/chopper_generator/lib/chopper_generator.dart b/chopper_generator/lib/chopper_generator.dart index e19b3343..2facc8b0 100644 --- a/chopper_generator/lib/chopper_generator.dart +++ b/chopper_generator/lib/chopper_generator.dart @@ -4,4 +4,4 @@ import 'package:build/build.dart'; import 'src/generator.dart'; Builder chopperGeneratorFactory(BuilderOptions options) => - chopperGeneratorFactoryBuilder(header: options.config['header'] as String); + chopperGeneratorFactoryBuilder(header: options.config['header']); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 1ad77a42..cadcfcbc 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -503,7 +503,7 @@ class ChopperGenerator extends GeneratorForAnnotation { } } -Builder chopperGeneratorFactoryBuilder({String header = ''}) => PartBuilder( +Builder chopperGeneratorFactoryBuilder({String? header}) => PartBuilder( [ChopperGenerator()], '.chopper.dart', header: header, From 3d4773562502a0f2996aa26d1dce157d5b30ea2f Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Mon, 31 May 2021 18:09:28 +0300 Subject: [PATCH 02/61] Fix for #259 (#263) --- chopper/lib/src/base.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index ff4bb051..be1d720e 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -314,11 +314,11 @@ class ChopperClient { var updatedRequest = await authenticator!.authenticate(request, res); if (updatedRequest != null) { - res = await send(updatedRequest); + res = await send(updatedRequest); } } - if (_responseIsSuccessful(response.statusCode)) { + if (_responseIsSuccessful(res.statusCode)) { res = await _handleSuccessResponse( res, responseConverter, From 8be965b94a38ab60ad17af04c8263ddb16eb14c9 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Tue, 1 Jun 2021 10:49:58 +0300 Subject: [PATCH 03/61] 4.0.1 fixes (#264) --- chopper/CHANGELOG.md | 15 +++++++++------ chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 13 ++++++++----- chopper_generator/pubspec.yaml | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index a3067275..6af68839 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 4.0.1 + +- Fix for the null safety support ## 4.0.0 - **Null safety support** @@ -52,7 +55,7 @@ New way to handle errors ## 2.4.2 -- Fix on JsonConverter +- Fix on JsonConverter If content type header overrided using @Post(headers: {'content-type': '...'}) The converter won't add json header and won't apply json.encode if content type is not JSON @@ -101,10 +104,10 @@ New way to handle errors ## 2.2.0 - Fix converter issue on List - - ***Breaking Change*** - on `Converter.convertResponse(response)`, + - ***Breaking Change*** + on `Converter.convertResponse(response)`, it take a new generic type => `Converter.convertResponse(response)` - + - deprecated `Chopper.service(Type)`, use `Chopper.getservice()` instead thanks to @MichaelDark @@ -139,12 +142,12 @@ thanks to @MichaelDark - ***BreakingChange*** Removed `name` parameter on `ChopperApi` New way to instanciate a service - + @ChopperApi() abstract class MyService extends ChopperService { static MyService create([ChopperClient client]) => _$MyService(client); } - + ## 1.0.0 diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 18ebf7d0..70d13169 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.0 +version: 4.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper author: Hadrien Lejard diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 48198a10..c6c3e8aa 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 4.0.1 + +- Fix for the null safety support ## 4.0.0 - **Null safety support** @@ -39,7 +42,7 @@ ## 2.4.2 -- Fix on JsonConverter +- Fix on JsonConverter If content type header overrided using @Post(headers: {'content-type': '...'}) The converter won't add json header and won't apply json.encode if content type is not JSON @@ -59,7 +62,7 @@ ## 2.3.4 fix trailing slash when empty path - + ## 2.3.3 - update analyzer to `0.35.0` @@ -91,10 +94,10 @@ ## 2.2.0 - Fix converter issue on List - - ***Breaking Change*** - on `Converter.convertResponse(response)`, + - ***Breaking Change*** + on `Converter.convertResponse(response)`, it take a new generic type => `Converter.convertResponse(response)` - + - deprecated `Chopper.service(Type)`, use `Chopper.getservice()` instead thanks to @MichaelDark diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 11ab6e4c..f399b818 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.0 +version: 4.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper author: Hadrien Lejard From bcb030489a4905d5c54c837e8a18149daefc3a9d Mon Sep 17 00:00:00 2001 From: luis901101 Date: Sat, 18 Sep 2021 02:16:37 -0400 Subject: [PATCH 04/61] analyzer dependency upgraded (#296) --- chopper_generator/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index f399b818..c877e588 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - analyzer: ^1.2.0 + analyzer: ^2.0.0 build: ^2.0.0 built_collection: ^5.0.0 chopper: ^4.0.0 From 4e4607e9c53012b3457542390af9b9886e0d1bc5 Mon Sep 17 00:00:00 2001 From: melvspace Date: Sat, 18 Sep 2021 09:28:55 +0300 Subject: [PATCH 05/61] fix(generator): fix PartValueFile value not nullable if arg is (#288) (#293) --- chopper_generator/lib/src/generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index cadcfcbc..40f722f6 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -456,7 +456,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ]; list.add( - refer('PartValueFile<${p.type.getDisplayString(withNullability: false)}>') + refer('PartValueFile<${p.type.getDisplayString(withNullability: p.type.isNullable)}>') .newInstance(params), ); }); From 939a83f11154d21a976cba52c9ac27e3111b5de1 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Thu, 23 Sep 2021 11:57:22 +0300 Subject: [PATCH 06/61] Chopper generator release 4.0.2 (#297) --- chopper_generator/CHANGELOG.md | 5 +++++ chopper_generator/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index c6c3e8aa..41173021 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 4.0.2 + +- Analyzer dependency upgrade +- PartValueFile nullability fix + ## 4.0.1 - Fix for the null safety support diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index c877e588..708c683c 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.1 +version: 4.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper author: Hadrien Lejard From ff0ec87317d969e402ff70321d3a7c3872561f8a Mon Sep 17 00:00:00 2001 From: melvspace Date: Thu, 23 Sep 2021 16:35:10 +0300 Subject: [PATCH 07/61] fix: fix this.body cast of null value when response body is null (#291) (#292) --- chopper/lib/src/response.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper/lib/src/response.dart b/chopper/lib/src/response.dart index 5b0e99c8..e4d9a56f 100644 --- a/chopper/lib/src/response.dart +++ b/chopper/lib/src/response.dart @@ -40,7 +40,7 @@ class Response { }) => Response( base ?? this.base, - body ?? (this.body as NewBodyType), + body ?? (this.body as NewBodyType?), error: bodyError ?? error, ); From ffcd94512586e5af41766f6f467ff5a2fcf922db Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Thu, 23 Sep 2021 17:42:24 +0300 Subject: [PATCH 08/61] Interpolation fixes (#275) --- chopper_generator/lib/src/generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 40f722f6..19630d2f 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -366,7 +366,7 @@ class ChopperGenerator extends GeneratorForAnnotation { var path = getMethodPath(method); paths.forEach((p, ConstantReader r) { final name = r.peek('name')?.stringValue ?? p.displayName; - path = path.replaceFirst('{$name}', '\$${p.displayName}'); + path = path.replaceFirst('{$name}', '\${${p.displayName}}'); }); if (path.startsWith('http://') || path.startsWith('https://')) { From 82b951fe278fcd783de5285b4ef2b7cbbede61c9 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Thu, 23 Sep 2021 17:42:36 +0300 Subject: [PATCH 09/61] encodeQueryComponent now encodeComponent (#278) --- chopper/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index 4e68b350..21b0605f 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -91,7 +91,7 @@ Iterable<_Pair> _iterableToQuery( ) => values.map((v) => _Pair(name, _normalizeValue(v))); -String _normalizeValue(value) => Uri.encodeQueryComponent(value.toString()); +String _normalizeValue(value) => Uri.encodeComponent(value.toString()); class _Pair { final A first; From f7255b768ce9ea6f153783575dd3f61ed8c77917 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Sun, 10 Oct 2021 09:20:43 +0300 Subject: [PATCH 10/61] Prevent double call on token refreshment (#276) --- chopper/lib/src/base.dart | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index be1d720e..1be36cb3 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -315,6 +315,13 @@ class ChopperClient { if (updatedRequest != null) { res = await send(updatedRequest); + // To prevent double call with typed response + if (_responseIsSuccessful(res.statusCode)) { + return _processResponse(res); + } else { + res = await _handleErrorResponse(res); + return _processResponse(res); + } } } @@ -327,10 +334,13 @@ class ChopperClient { res = await _handleErrorResponse(res); } - res = await _interceptResponse(res); + return _processResponse(res); + } + Future> _processResponse( + dynamic res) async { + res = await _interceptResponse(res); _responseController.add(res); - return res; } From ddefa948e58cdb96232e96cdfeb429e66c0537e2 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Tue, 7 Dec 2021 19:57:13 +0300 Subject: [PATCH 11/61] Fixes for #309 #308 (#310) --- chopper/lib/src/base.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 1be36cb3..c5cbeb06 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -311,10 +311,14 @@ class ChopperClient { dynamic res = Response(response, response.body); if (authenticator != null) { - var updatedRequest = await authenticator!.authenticate(request, res); + var updatedRequest = await authenticator!.authenticate(req, res); if (updatedRequest != null) { - res = await send(updatedRequest); + res = await send( + updatedRequest, + requestConverter: requestConverter, + responseConverter: responseConverter, + ); // To prevent double call with typed response if (_responseIsSuccessful(res.statusCode)) { return _processResponse(res); From 0386c74a53a74842a924f8711884ff9b6af45b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=A0r=C5=AFtek?= <35694712+michalsrutek@users.noreply.github.com> Date: Mon, 3 Jan 2022 21:06:32 +0100 Subject: [PATCH 12/61] Remove new keyword from interceptors.md (#312) --- interceptors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interceptors.md b/interceptors.md index 541d5263..2aadab1b 100644 --- a/interceptors.md +++ b/interceptors.md @@ -7,7 +7,7 @@ Implement `RequestInterceptor` class or define function with following signature Request interceptor are called just before sending request ```dart -final chopper = new ChopperClient( +final chopper = ChopperClient( interceptors: [ (request) async => request.copyWith(body: {}), ] @@ -23,7 +23,7 @@ Called after successful or failed request {% endhint %} ```dart -final chopper = new ChopperClient( +final chopper = ChopperClient( interceptors: [ (Response response) async => response.replace(body: {}), ] From d4dab0d5ac7273227853f3f9801eba73b81aa282 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Sun, 16 Jan 2022 09:21:04 +0300 Subject: [PATCH 13/61] Analyzer upgrade (#320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: István Juhos --- .github/workflows/dart.yml | 167 +++++++++++++---------- chopper/example/main.dart | 1 - chopper/mono_pkg.yaml | 10 +- chopper/pubspec.yaml | 1 - chopper/test/base_test.dart | 7 - chopper/test/converter_test.dart | 4 - chopper/test/test_service.dart | 1 - chopper_built_value/mono_pkg.yaml | 8 +- chopper_built_value/pubspec.yaml | 1 - chopper_generator/lib/src/generator.dart | 1 - chopper_generator/mono_pkg.yaml | 8 +- chopper_generator/pubspec.yaml | 3 +- docs/ci/ci_setup.md | 9 ++ tool/ci.sh | 59 ++++---- 14 files changed, 153 insertions(+), 127 deletions(-) create mode 100644 docs/ci/ci_setup.md diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index bfe02c10..9bf38a8f 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v3.4.7 +# Created with package:mono_repo v6.0.0 name: Dart CI on: push: @@ -21,161 +21,185 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2 + uses: actions/cache@v2.1.7 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;dart:stable" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable" restore-keys: | os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 - name: mono_repo self validate - run: pub global activate mono_repo 3.4.7 + run: dart pub global activate mono_repo 6.0.0 - name: mono_repo self validate - run: pub global run mono_repo generate --validate + run: dart pub global run mono_repo generate --validate job_002: - name: "analyzer_and_format; PKGS: chopper, chopper_built_value, chopper_generator; `dartfmt -n --set-exit-if-changed .`, `dartanalyzer --fatal-infos .`" + name: "analyzer_and_format; PKGS: chopper_built_value, chopper_generator; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2 + uses: actions/cache@v2.1.7 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value-chopper_generator;commands:dartfmt-dartanalyzer" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper_built_value-chopper_generator;commands:format-analyze" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value-chopper_generator - os:ubuntu-latest;pub-cache-hosted;dart:stable + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper_built_value-chopper_generator + os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 - - id: chopper_pub_upgrade - name: "chopper; pub upgrade --no-precompile" - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: chopper - run: pub upgrade --no-precompile - - name: "chopper; dartfmt -n --set-exit-if-changed ." - if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" - working-directory: chopper - run: dartfmt -n --set-exit-if-changed . - - name: "chopper; dartanalyzer --fatal-infos ." - if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" - working-directory: chopper - run: dartanalyzer --fatal-infos . + uses: actions/checkout@v2.4.0 - id: chopper_built_value_pub_upgrade - name: "chopper_built_value; pub upgrade --no-precompile" + name: chopper_built_value; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: pub upgrade --no-precompile - - name: "chopper_built_value; dartfmt -n --set-exit-if-changed ." + run: dart pub upgrade + - name: "chopper_built_value; dart format --output=none --set-exit-if-changed ." if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: dartfmt -n --set-exit-if-changed . - - name: "chopper_built_value; dartanalyzer --fatal-infos ." + run: "dart format --output=none --set-exit-if-changed ." + - name: "chopper_built_value; dart analyze --fatal-infos ." if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: dartanalyzer --fatal-infos . + run: dart analyze --fatal-infos . - id: chopper_generator_pub_upgrade - name: "chopper_generator; pub upgrade --no-precompile" + name: chopper_generator; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_generator - run: pub upgrade --no-precompile - - name: "chopper_generator; dartfmt -n --set-exit-if-changed ." + run: dart pub upgrade + - name: "chopper_generator; dart format --output=none --set-exit-if-changed ." if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - run: dartfmt -n --set-exit-if-changed . - - name: "chopper_generator; dartanalyzer --fatal-infos ." + run: "dart format --output=none --set-exit-if-changed ." + - name: "chopper_generator; dart analyze --fatal-infos ." if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - run: dartanalyzer --fatal-infos . + run: dart analyze --fatal-infos . job_003: - name: "unit_test; PKGS: chopper, chopper_built_value; `pub run test`" + name: "analyze_and_format; PKG: chopper; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2 + uses: actions/cache@v2.1.7 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value;commands:test_0" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value - os:ubuntu-latest;pub-cache-hosted;dart:stable + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper + os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 + - id: chopper_pub_upgrade + name: chopper; dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: chopper + run: dart pub upgrade + - name: "chopper; dart format --output=none --set-exit-if-changed ." + if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" + working-directory: chopper + run: "dart format --output=none --set-exit-if-changed ." + - name: "chopper; dart analyze --fatal-infos ." + if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" + working-directory: chopper + run: dart analyze --fatal-infos . + needs: + - job_001 + - job_002 + job_004: + name: "unit_test; PKGS: chopper, chopper_built_value; `dart test -p chrome`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@v2.1.7 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_1" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value + os:ubuntu-latest;pub-cache-hosted;sdk:stable + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - uses: dart-lang/setup-dart@v1.3 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v2.4.0 - id: chopper_pub_upgrade - name: "chopper; pub upgrade --no-precompile" + name: chopper; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - run: pub upgrade --no-precompile - - name: chopper; pub run test + run: dart pub upgrade + - name: "chopper; dart test -p chrome" if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: pub run test + run: dart test -p chrome - id: chopper_built_value_pub_upgrade - name: "chopper_built_value; pub upgrade --no-precompile" + name: chopper_built_value; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: pub upgrade --no-precompile - - name: chopper_built_value; pub run test + run: dart pub upgrade + - name: "chopper_built_value; dart test -p chrome" if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: pub run test + run: dart test -p chrome needs: - job_001 - job_002 - job_004: - name: "unit_test; PKGS: chopper, chopper_built_value; `pub run test -p chrome`" + - job_003 + job_005: + name: "unit_test; PKGS: chopper, chopper_built_value; `dart test`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2 + uses: actions/cache@v2.1.7 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value;commands:test_1" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_0" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;dart:stable;packages:chopper-chopper_built_value - os:ubuntu-latest;pub-cache-hosted;dart:stable + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value + os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 - id: chopper_pub_upgrade - name: "chopper; pub upgrade --no-precompile" + name: chopper; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - run: pub upgrade --no-precompile - - name: "chopper; pub run test -p chrome" + run: dart pub upgrade + - name: chopper; dart test if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: pub run test -p chrome + run: dart test - id: chopper_built_value_pub_upgrade - name: "chopper_built_value; pub upgrade --no-precompile" + name: chopper_built_value; dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: pub upgrade --no-precompile - - name: "chopper_built_value; pub run test -p chrome" + run: dart pub upgrade + - name: chopper_built_value; dart test if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: pub run test -p chrome + run: dart test needs: - job_001 - job_002 - job_005: + - job_003 + job_006: name: Coverage runs-on: ubuntu-latest steps: @@ -195,3 +219,4 @@ jobs: - job_002 - job_003 - job_004 + - job_005 diff --git a/chopper/example/main.dart b/chopper/example/main.dart index 01070815..d1831859 100644 --- a/chopper/example/main.dart +++ b/chopper/example/main.dart @@ -1,5 +1,4 @@ import 'package:chopper/chopper.dart'; -import 'package:chopper/src/interceptor.dart'; import 'definition.dart'; Future main() async { diff --git a/chopper/mono_pkg.yaml b/chopper/mono_pkg.yaml index f1dd9d0f..ed853726 100644 --- a/chopper/mono_pkg.yaml +++ b/chopper/mono_pkg.yaml @@ -1,11 +1,11 @@ -dart: -- stable +sdk: + - stable stages: -- analyzer_and_format: +- analyze_and_format: - group: - - dartfmt - - dartanalyzer: --fatal-infos . + - format + - analyze: --fatal-infos . - unit_test: - test: - test: -p chrome diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 70d13169..d2e41a97 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -3,7 +3,6 @@ description: Chopper is an http client generator using source_gen, inspired by R version: 4.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper -author: Hadrien Lejard environment: sdk: ">=2.12.0 <3.0.0" diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index d487184d..b591168a 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -22,13 +22,6 @@ void main() { errorConverter: errorConverter, ); group('Base', () { - test('get service', () async { - final chopper = buildClient(); - final service = chopper.getService(); - - expect(service is HttpTestService, isTrue); - }); - test('get service errors', () async { final chopper = ChopperClient( baseUrl: baseUrl, diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 1941445b..7bc98b58 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -72,7 +72,6 @@ void main() { final res = Response(http.Response('"$value"', 200), '"$value"'); final converted = jsonConverter.convertResponse(res); - expect(converted is Response, isTrue); expect(converted.body, equals(value)); }); @@ -84,7 +83,6 @@ void main() { final converted = jsonConverter.convertResponse, String>(res); - expect(converted is Response>, isTrue); expect(converted.body, equals(['foo', 'bar'])); }); @@ -92,7 +90,6 @@ void main() { final res = Response(http.Response('[1,2]', 200), '[1,2]'); final converted = jsonConverter.convertResponse, int>(res); - expect(converted is Response>, isTrue); expect(converted.body, equals([1, 2])); }); @@ -104,7 +101,6 @@ void main() { final converted = jsonConverter.convertResponse, String>(res); - expect(converted is Response>, isTrue); expect(converted.body, equals({'foo': 'bar'})); }); }); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 426b045d..1c68866a 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'package:chopper/chopper.dart'; -import 'package:chopper/src/constants.dart'; import 'package:http/http.dart' show MultipartFile; diff --git a/chopper_built_value/mono_pkg.yaml b/chopper_built_value/mono_pkg.yaml index f1dd9d0f..3d4d539a 100644 --- a/chopper_built_value/mono_pkg.yaml +++ b/chopper_built_value/mono_pkg.yaml @@ -1,11 +1,11 @@ -dart: -- stable +sdk: + - stable stages: - analyzer_and_format: - group: - - dartfmt - - dartanalyzer: --fatal-infos . + - format + - analyze: --fatal-infos . - unit_test: - test: - test: -p chrome diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 4f8b59b7..88ccd7fc 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -3,7 +3,6 @@ description: A built_value based Converter for Chopper. version: 1.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper -author: Hadrien Lejard environment: sdk: ">=2.12.0 <3.0.0" diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 19630d2f..bae30db8 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -6,7 +6,6 @@ import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; -import 'package:build/src/builder/build_step.dart'; import 'package:built_collection/built_collection.dart'; import 'package:dart_style/dart_style.dart'; diff --git a/chopper_generator/mono_pkg.yaml b/chopper_generator/mono_pkg.yaml index be29992e..0620d98d 100644 --- a/chopper_generator/mono_pkg.yaml +++ b/chopper_generator/mono_pkg.yaml @@ -1,11 +1,11 @@ -dart: -- stable +sdk: + - stable stages: - analyzer_and_format: - group: - - dartfmt - - dartanalyzer: --fatal-infos . + - format + - analyze: --fatal-infos . cache: directories: diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 708c683c..a88b8b97 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -3,13 +3,12 @@ description: Chopper is an http client generator using source_gen, inspired by R version: 4.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper -author: Hadrien Lejard environment: sdk: ">=2.12.0 <3.0.0" dependencies: - analyzer: ^2.0.0 + analyzer: ^3.0.0 build: ^2.0.0 built_collection: ^5.0.0 chopper: ^4.0.0 diff --git a/docs/ci/ci_setup.md b/docs/ci/ci_setup.md new file mode 100644 index 00000000..99dca171 --- /dev/null +++ b/docs/ci/ci_setup.md @@ -0,0 +1,9 @@ +# The CI setup of the project + +⚠️ This document is heavily WIP. It will contain the full CI setup guide for this project. + +## Generating the CI config + +We use the [`mono_repo`](https://pub.dev/packages/mono_repo) Dart package project for generating the GitHub CI config. + +To install and use `mono_repo`, refer to its official documentation linked above. \ No newline at end of file diff --git a/tool/ci.sh b/tool/ci.sh index f7a6d3f0..d614ed80 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,26 +1,35 @@ #!/bin/bash -# Created with package:mono_repo v3.4.7 +# Created with package:mono_repo v6.0.0 # Support built in commands on windows out of the box. +# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") +# then "flutter" is called instead of "pub". +# This assumes that the Flutter SDK has been installed in a previous step. function pub() { - if [[ $TRAVIS_OS_NAME == "windows" ]]; then - command pub.bat "$@" + if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then + command flutter pub "$@" else - command pub "$@" + command dart pub "$@" fi } -function dartfmt() { - if [[ $TRAVIS_OS_NAME == "windows" ]]; then - command dartfmt.bat "$@" +# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") +# then "flutter" is called instead of "pub". +# This assumes that the Flutter SDK has been installed in a previous step. +function format() { + if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then + command flutter format "$@" else - command dartfmt "$@" + command dart format "$@" fi } -function dartanalyzer() { - if [[ $TRAVIS_OS_NAME == "windows" ]]; then - command dartanalyzer.bat "$@" +# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") +# then "flutter" is called instead of "pub". +# This assumes that the Flutter SDK has been installed in a previous step. +function analyze() { + if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then + command flutter analyze "$@" else - command dartanalyzer "$@" + command dart analyze "$@" fi } @@ -47,32 +56,32 @@ for PKG in ${PKGS}; do exit 64 fi - pub upgrade --no-precompile || EXIT_CODE=$? + dart pub upgrade || EXIT_CODE=$? if [[ ${EXIT_CODE} -ne 0 ]]; then - echo -e "\033[31mPKG: ${PKG}; 'pub upgrade' - FAILED (${EXIT_CODE})\033[0m" - FAILURES+=("${PKG}; 'pub upgrade'") + echo -e "\033[31mPKG: ${PKG}; 'dart pub upgrade' - FAILED (${EXIT_CODE})\033[0m" + FAILURES+=("${PKG}; 'dart pub upgrade'") else for TASK in "$@"; do EXIT_CODE=0 echo echo -e "\033[1mPKG: ${PKG}; TASK: ${TASK}\033[22m" case ${TASK} in - dartanalyzer) - echo 'dartanalyzer --fatal-infos .' - dartanalyzer --fatal-infos . || EXIT_CODE=$? + analyze) + echo 'dart analyze --fatal-infos .' + dart analyze --fatal-infos . || EXIT_CODE=$? ;; - dartfmt) - echo 'dartfmt -n --set-exit-if-changed .' - dartfmt -n --set-exit-if-changed . || EXIT_CODE=$? + format) + echo 'dart format --output=none --set-exit-if-changed .' + dart format --output=none --set-exit-if-changed . || EXIT_CODE=$? ;; test_0) - echo 'pub run test' - pub run test || EXIT_CODE=$? + echo 'dart test' + dart test || EXIT_CODE=$? ;; test_1) - echo 'pub run test -p chrome' - pub run test -p chrome || EXIT_CODE=$? + echo 'dart test -p chrome' + dart test -p chrome || EXIT_CODE=$? ;; *) echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m" From bbe2c7ad92a1a4839b2c6eb5bf0d67f05c639ef1 Mon Sep 17 00:00:00 2001 From: Andre Date: Sun, 16 Jan 2022 15:21:56 -0500 Subject: [PATCH 14/61] Add unnecessary_brace_in_string_interps to lint ignores (#317) --- chopper_generator/lib/src/generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index bae30db8..ccf42daf 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -82,7 +82,7 @@ class ChopperGenerator extends GeneratorForAnnotation { }); final ignore = - '// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations'; + '// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps'; final emitter = DartEmitter(); return DartFormatter().format('$ignore\n${classBuilder.accept(emitter)}'); } From 6bdbd635e254d62b91b47b14e41fe94c9dcb2697 Mon Sep 17 00:00:00 2001 From: John Wimer Date: Sat, 22 Jan 2022 10:36:47 +0100 Subject: [PATCH 15/61] Extend pragma to quiet the linter (#318) Co-authored-by: Ivan Terekhin From 4185d140f7e639e66ab6d4457391171573ad717e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20R=C3=B6hrl?= Date: Sun, 30 Jan 2022 09:40:41 +0100 Subject: [PATCH 16/61] Fix converter getting called twice if using an authenticator with a JsonConverter on the request (#324) --- chopper/lib/src/authenticator.dart | 3 ++- chopper/lib/src/base.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/authenticator.dart b/chopper/lib/src/authenticator.dart index 62d5c692..db225a49 100644 --- a/chopper/lib/src/authenticator.dart +++ b/chopper/lib/src/authenticator.dart @@ -5,5 +5,6 @@ import 'package:chopper/chopper.dart'; /// This method should return a [Request] that includes credentials to satisfy an authentication challenge received in /// [response]. It should return `null` if the challenge cannot be satisfied. abstract class Authenticator { - FutureOr authenticate(Request request, Response response); + FutureOr authenticate(Request request, Response response, + [Request? originalRequest]); } diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index c5cbeb06..b0f90848 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -311,7 +311,7 @@ class ChopperClient { dynamic res = Response(response, response.body); if (authenticator != null) { - var updatedRequest = await authenticator!.authenticate(req, res); + var updatedRequest = await authenticator!.authenticate(req, res, request); if (updatedRequest != null) { res = await send( From dbf427225eb02e1bb67ea93b97b90713bfd5958e Mon Sep 17 00:00:00 2001 From: ipcjs Date: Wed, 23 Feb 2022 02:54:36 +0800 Subject: [PATCH 17/61] migrate example to nullsafety (#331) --- README.md | 1 - chopper/README.md | 1 - example/bin/main_built_value.dart | 17 +- example/bin/main_json_serializable.dart | 8 +- example/lib/angular_example.dart | 22 - example/lib/built_value_resource.chopper.dart | 18 +- example/lib/built_value_resource.dart | 4 +- example/lib/built_value_resource.g.dart | 109 +++-- example/lib/built_value_serializers.g.dart | 2 +- example/lib/json_serializable.chopper.dart | 23 +- example/lib/json_serializable.dart | 7 +- example/lib/json_serializable.g.dart | 21 +- example/pubspec.lock | 425 ++++++++++++++++++ example/pubspec.yaml | 29 +- example/web/index.html | 20 - example/web/main.dart | 34 -- 16 files changed, 553 insertions(+), 188 deletions(-) delete mode 100644 example/lib/angular_example.dart create mode 100644 example/pubspec.lock delete mode 100644 example/web/index.html delete mode 100644 example/web/main.dart diff --git a/README.md b/README.md index a477ce40..a0b3f6a5 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ Please refer to the installation guide at [pub.dev](https://pub.dev/packages/cho * [json serializable Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) * [built value Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) -* [Angular](https://github.com/lejard-h/chopper/blob/master/example/web/main.dart) ## [Issue Tracker](https://github.com/lejard-h/chopper/issues) diff --git a/chopper/README.md b/chopper/README.md index eaac504c..b3453bef 100644 --- a/chopper/README.md +++ b/chopper/README.md @@ -45,6 +45,5 @@ Latest versions: * [json_serializable Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_json_serializable.dart) * [built_value Converter](https://github.com/lejard-h/chopper/blob/master/example/bin/main_built_value.dart) -* [Angular](https://github.com/lejard-h/chopper/blob/master/example/web/main.dart) ## If you encounter any issues, or need a feature implemented, please visit [Chopper's Issue Tracker on GitHub](https://github.com/lejard-h/chopper/issues). diff --git a/example/bin/main_built_value.dart b/example/bin/main_built_value.dart index 99ec99be..8ebaff5b 100644 --- a/example/bin/main_built_value.dart +++ b/example/bin/main_built_value.dart @@ -1,4 +1,5 @@ import 'package:built_collection/built_collection.dart'; +import 'package:built_value/serializer.dart'; import 'package:chopper/chopper.dart'; import 'package:chopper_example/built_value_resource.dart'; import 'package:chopper_example/built_value_serializers.dart'; @@ -52,16 +53,22 @@ main() async { } class BuiltValueConverter extends JsonConverter { - T _deserialize(dynamic value) => jsonSerializers.deserializeWith( - jsonSerializers.serializerForType(T), - value, - ); + T? _deserialize(dynamic value) { + final serializer = jsonSerializers.serializerForType(T) as Serializer?; + if (serializer == null) { + throw Exception('No serializer for type ${T}'); + } + return jsonSerializers.deserializeWith( + serializer, + value, + ); + } BuiltList _deserializeListOf(Iterable value) => BuiltList( value.map((value) => _deserialize(value)).toList(growable: false), ); - dynamic _decode(entity) { + dynamic _decode(dynamic entity) { /// handle case when we want to access to Map directly /// getResource or getMapResource /// Avoid dynamic or unconverted value, this could lead to several issues diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index 8fab276d..eb7fccfc 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -67,7 +67,7 @@ class JsonSerializableConverter extends JsonConverter { JsonSerializableConverter(this.factories); - T _decodeMap(Map values) { + T? _decodeMap(Map values) { /// Get jsonFactory using Type parameters /// if not found or invalid, throw error or return null final jsonFactory = factories[T]; @@ -79,13 +79,13 @@ class JsonSerializableConverter extends JsonConverter { return jsonFactory(values); } - List _decodeList(List values) => + List _decodeList(Iterable values) => values.where((v) => v != null).map((v) => _decode(v)).toList(); - dynamic _decode(entity) { + dynamic _decode(dynamic entity) { if (entity is Iterable) return _decodeList(entity); - if (entity is Map) return _decodeMap(entity); + if (entity is Map) return _decodeMap(entity); return entity; } diff --git a/example/lib/angular_example.dart b/example/lib/angular_example.dart deleted file mode 100644 index 09d688f3..00000000 --- a/example/lib/angular_example.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:angular/angular.dart'; -import 'package:chopper/chopper.dart'; -import 'package:chopper_example/built_value_resource.dart'; - -// ignore: uri_has_not_been_generated -import 'angular_example.template.dart' as ng; - -final appFactory = ng.ChopperExampleComponentNgFactory; - -MyService serviceFactory(ChopperClient client) => MyService.create(client); - -@Component( - selector: 'app-component', - template: '{{client}} {{service}}', - providers: [FactoryProvider(MyService, serviceFactory)], -) -class ChopperExampleComponent { - final ChopperClient client; - final MyService service; - - ChopperExampleComponent(this.client, this.service); -} diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index f18fe2b4..c092d4b0 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -6,9 +6,9 @@ part of resource; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$MyService extends MyService { - _$MyService([ChopperClient client]) { + _$MyService([ChopperClient? client]) { if (client == null) return; this.client = client; } @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/$id/'; + final $url = '/resources/${id}/'; final $request = Request('GET', $url, client.baseUrl); return client.send($request); } @@ -33,15 +33,21 @@ class _$MyService extends MyService { @override Future> getTypedResource() { final $url = '/resources/'; - final $headers = {'foo': 'bar'}; + final $headers = { + 'foo': 'bar', + }; + final $request = Request('GET', $url, client.baseUrl, headers: $headers); return client.send($request); } @override - Future> newResource(Resource resource, {String name}) { + Future> newResource(Resource resource, {String? name}) { final $url = '/resources'; - final $headers = {'name': name}; + final $headers = { + if (name != null) 'name': name, + }; + final $body = resource; final $request = Request('POST', $url, client.baseUrl, body: $body, headers: $headers); diff --git a/example/lib/built_value_resource.dart b/example/lib/built_value_resource.dart index f49131e5..6ea4e35b 100644 --- a/example/lib/built_value_resource.dart +++ b/example/lib/built_value_resource.dart @@ -33,7 +33,7 @@ abstract class ResourceError @ChopperApi(baseUrl: "/resources") abstract class MyService extends ChopperService { - static MyService create([ChopperClient client]) => _$MyService(client); + static MyService create([ChopperClient? client]) => _$MyService(client); @Get(path: "/{id}/") Future getResource(@Path() String id); @@ -46,5 +46,5 @@ abstract class MyService extends ChopperService { @Post() Future> newResource(@Body() Resource resource, - {@Header() String name}); + {@Header() String? name}); } diff --git a/example/lib/built_value_resource.g.dart b/example/lib/built_value_resource.g.dart index 90a3d5e4..8030092d 100644 --- a/example/lib/built_value_resource.g.dart +++ b/example/lib/built_value_resource.g.dart @@ -17,9 +17,9 @@ class _$ResourceSerializer implements StructuredSerializer { final String wireName = 'Resource'; @override - Iterable serialize(Serializers serializers, Resource object, + Iterable serialize(Serializers serializers, Resource object, {FullType specifiedType = FullType.unspecified}) { - final result = [ + final result = [ 'id', serializers.serialize(object.id, specifiedType: const FullType(String)), 'name', @@ -30,7 +30,7 @@ class _$ResourceSerializer implements StructuredSerializer { } @override - Resource deserialize(Serializers serializers, Iterable serialized, + Resource deserialize(Serializers serializers, Iterable serialized, {FullType specifiedType = FullType.unspecified}) { final result = new ResourceBuilder(); @@ -38,7 +38,7 @@ class _$ResourceSerializer implements StructuredSerializer { while (iterator.moveNext()) { final key = iterator.current as String; iterator.moveNext(); - final dynamic value = iterator.current; + final Object? value = iterator.current; switch (key) { case 'id': result.id = serializers.deserialize(value, @@ -62,9 +62,9 @@ class _$ResourceErrorSerializer implements StructuredSerializer { final String wireName = 'ResourceError'; @override - Iterable serialize(Serializers serializers, ResourceError object, + Iterable serialize(Serializers serializers, ResourceError object, {FullType specifiedType = FullType.unspecified}) { - final result = [ + final result = [ 'type', serializers.serialize(object.type, specifiedType: const FullType(String)), 'message', @@ -77,7 +77,7 @@ class _$ResourceErrorSerializer implements StructuredSerializer { @override ResourceError deserialize( - Serializers serializers, Iterable serialized, + Serializers serializers, Iterable serialized, {FullType specifiedType = FullType.unspecified}) { final result = new ResourceErrorBuilder(); @@ -85,7 +85,7 @@ class _$ResourceErrorSerializer implements StructuredSerializer { while (iterator.moveNext()) { final key = iterator.current as String; iterator.moveNext(); - final dynamic value = iterator.current; + final Object? value = iterator.current; switch (key) { case 'type': result.type = serializers.deserialize(value, @@ -108,16 +108,12 @@ class _$Resource extends Resource { @override final String name; - factory _$Resource([void Function(ResourceBuilder) updates]) => + factory _$Resource([void Function(ResourceBuilder)? updates]) => (new ResourceBuilder()..update(updates)).build(); - _$Resource._({this.id, this.name}) : super._() { - if (id == null) { - throw new BuiltValueNullFieldError('Resource', 'id'); - } - if (name == null) { - throw new BuiltValueNullFieldError('Resource', 'name'); - } + _$Resource._({required this.id, required this.name}) : super._() { + BuiltValueNullFieldError.checkNotNull(id, 'Resource', 'id'); + BuiltValueNullFieldError.checkNotNull(name, 'Resource', 'name'); } @override @@ -148,22 +144,23 @@ class _$Resource extends Resource { } class ResourceBuilder implements Builder { - _$Resource _$v; + _$Resource? _$v; - String _id; - String get id => _$this._id; - set id(String id) => _$this._id = id; + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; - String _name; - String get name => _$this._name; - set name(String name) => _$this._name = name; + String? _name; + String? get name => _$this._name; + set name(String? name) => _$this._name = name; ResourceBuilder(); ResourceBuilder get _$this { - if (_$v != null) { - _id = _$v.id; - _name = _$v.name; + final $v = _$v; + if ($v != null) { + _id = $v.id; + _name = $v.name; _$v = null; } return this; @@ -171,20 +168,22 @@ class ResourceBuilder implements Builder { @override void replace(Resource other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } + ArgumentError.checkNotNull(other, 'other'); _$v = other as _$Resource; } @override - void update(void Function(ResourceBuilder) updates) { + void update(void Function(ResourceBuilder)? updates) { if (updates != null) updates(this); } @override _$Resource build() { - final _$result = _$v ?? new _$Resource._(id: id, name: name); + final _$result = _$v ?? + new _$Resource._( + id: BuiltValueNullFieldError.checkNotNull(id, 'Resource', 'id'), + name: BuiltValueNullFieldError.checkNotNull( + name, 'Resource', 'name')); replace(_$result); return _$result; } @@ -196,16 +195,12 @@ class _$ResourceError extends ResourceError { @override final String message; - factory _$ResourceError([void Function(ResourceErrorBuilder) updates]) => + factory _$ResourceError([void Function(ResourceErrorBuilder)? updates]) => (new ResourceErrorBuilder()..update(updates)).build(); - _$ResourceError._({this.type, this.message}) : super._() { - if (type == null) { - throw new BuiltValueNullFieldError('ResourceError', 'type'); - } - if (message == null) { - throw new BuiltValueNullFieldError('ResourceError', 'message'); - } + _$ResourceError._({required this.type, required this.message}) : super._() { + BuiltValueNullFieldError.checkNotNull(type, 'ResourceError', 'type'); + BuiltValueNullFieldError.checkNotNull(message, 'ResourceError', 'message'); } @override @@ -239,22 +234,23 @@ class _$ResourceError extends ResourceError { class ResourceErrorBuilder implements Builder { - _$ResourceError _$v; + _$ResourceError? _$v; - String _type; - String get type => _$this._type; - set type(String type) => _$this._type = type; + String? _type; + String? get type => _$this._type; + set type(String? type) => _$this._type = type; - String _message; - String get message => _$this._message; - set message(String message) => _$this._message = message; + String? _message; + String? get message => _$this._message; + set message(String? message) => _$this._message = message; ResourceErrorBuilder(); ResourceErrorBuilder get _$this { - if (_$v != null) { - _type = _$v.type; - _message = _$v.message; + final $v = _$v; + if ($v != null) { + _type = $v.type; + _message = $v.message; _$v = null; } return this; @@ -262,23 +258,26 @@ class ResourceErrorBuilder @override void replace(ResourceError other) { - if (other == null) { - throw new ArgumentError.notNull('other'); - } + ArgumentError.checkNotNull(other, 'other'); _$v = other as _$ResourceError; } @override - void update(void Function(ResourceErrorBuilder) updates) { + void update(void Function(ResourceErrorBuilder)? updates) { if (updates != null) updates(this); } @override _$ResourceError build() { - final _$result = _$v ?? new _$ResourceError._(type: type, message: message); + final _$result = _$v ?? + new _$ResourceError._( + type: BuiltValueNullFieldError.checkNotNull( + type, 'ResourceError', 'type'), + message: BuiltValueNullFieldError.checkNotNull( + message, 'ResourceError', 'message')); replace(_$result); return _$result; } } -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/example/lib/built_value_serializers.g.dart b/example/lib/built_value_serializers.g.dart index 2863e337..dbad2232 100644 --- a/example/lib/built_value_serializers.g.dart +++ b/example/lib/built_value_serializers.g.dart @@ -11,4 +11,4 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ResourceError.serializer)) .build(); -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index 55ee0d31..bd44d2f3 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -6,9 +6,9 @@ part of 'json_serializable.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$MyService extends MyService { - _$MyService([ChopperClient client]) { + _$MyService([ChopperClient? client]) { if (client == null) return; this.client = client; } @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/$id/'; + final $url = '/resources/${id}/'; final $request = Request('GET', $url, client.baseUrl); return client.send($request); } @@ -26,7 +26,10 @@ class _$MyService extends MyService { @override Future>> getResources() { final $url = '/resources/all'; - final $headers = {'test': 'list'}; + final $headers = { + 'test': 'list', + }; + final $request = Request('GET', $url, client.baseUrl, headers: $headers); return client.send, Resource>($request); } @@ -42,15 +45,21 @@ class _$MyService extends MyService { @override Future> getTypedResource() { final $url = '/resources/'; - final $headers = {'foo': 'bar'}; + final $headers = { + 'foo': 'bar', + }; + final $request = Request('GET', $url, client.baseUrl, headers: $headers); return client.send($request); } @override - Future> newResource(Resource resource, {String name}) { + Future> newResource(Resource resource, {String? name}) { final $url = '/resources'; - final $headers = {'name': name}; + final $headers = { + if (name != null) 'name': name, + }; + final $body = resource; final $request = Request('POST', $url, client.baseUrl, body: $body, headers: $headers); diff --git a/example/lib/json_serializable.dart b/example/lib/json_serializable.dart index ceca95d0..361f166a 100644 --- a/example/lib/json_serializable.dart +++ b/example/lib/json_serializable.dart @@ -16,6 +16,9 @@ class Resource { static const fromJsonFactory = _$ResourceFromJson; Map toJson() => _$ResourceToJson(this); + + @override + String toString() => 'Resource{id: $id, name: $name}'; } @JsonSerializable() @@ -32,7 +35,7 @@ class ResourceError { @ChopperApi(baseUrl: "/resources") abstract class MyService extends ChopperService { - static MyService create([ChopperClient client]) => _$MyService(client); + static MyService create([ChopperClient? client]) => _$MyService(client); @Get(path: "/{id}/") Future getResource(@Path() String id); @@ -48,5 +51,5 @@ abstract class MyService extends ChopperService { @Post() Future> newResource(@Body() Resource resource, - {@Header() String name}); + {@Header() String? name}); } diff --git a/example/lib/json_serializable.g.dart b/example/lib/json_serializable.g.dart index e8cb2986..bb2b4f95 100644 --- a/example/lib/json_serializable.g.dart +++ b/example/lib/json_serializable.g.dart @@ -6,24 +6,21 @@ part of 'json_serializable.dart'; // JsonSerializableGenerator // ************************************************************************** -Resource _$ResourceFromJson(Map json) { - return Resource( - json['id'] as String, - json['name'] as String, - ); -} +Resource _$ResourceFromJson(Map json) => Resource( + json['id'] as String, + json['name'] as String, + ); Map _$ResourceToJson(Resource instance) => { 'id': instance.id, 'name': instance.name, }; -ResourceError _$ResourceErrorFromJson(Map json) { - return ResourceError( - json['type'] as String, - json['message'] as String, - ); -} +ResourceError _$ResourceErrorFromJson(Map json) => + ResourceError( + json['type'] as String, + json['message'] as String, + ); Map _$ResourceErrorToJson(ResourceError instance) => { diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 00000000..fff2f649 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,425 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "34.0.0" + analyzer: + dependency: "direct main" + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: "direct main" + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.4" + built_value_generator: + dependency: "direct dev" + description: + name: built_value_generator + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.4" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + chopper: + dependency: "direct main" + description: + path: "../chopper" + relative: true + source: path + version: "4.0.1" + chopper_generator: + dependency: "direct dev" + description: + path: "../chopper_generator" + relative: true + source: path + version: "4.0.2" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1+1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + source_helper: + dependency: transitive + description: + name: source_helper + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.16.0-100.0.dev <3.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 515ae565..9a88feed 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,28 +2,25 @@ name: chopper_example description: Example usage of the Chopper package version: 0.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper/ -author: Hadrien Lejard +#author: Hadrien Lejard environment: sdk: '>=2.12.0 <3.0.0' dependencies: - angular: ^6.0.1 - chopper: ^3.0.0 - json_annotation: ^4.0.0 - built_value: ^8.0.0 - analyzer: ^1.2.0 + chopper: + json_annotation: + built_value: + analyzer: dev_dependencies: - build_runner: ^1.12.1 - chopper_generator: ^3.0.6 - build_web_compilers: ^2.0.0 - json_serializable: ^4.0.2 - built_value_generator: ^8.0.0 + build_runner: + chopper_generator: + json_serializable: + built_value_generator: dependency_overrides: -# chopper: -# path: ../chopper -# chopper_generator: -# path: ../chopper_generator - \ No newline at end of file + chopper: + path: ../chopper + chopper_generator: + path: ../chopper_generator diff --git a/example/web/index.html b/example/web/index.html deleted file mode 100644 index 79681fa5..00000000 --- a/example/web/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - Chopper - - - - - - - - - \ No newline at end of file diff --git a/example/web/main.dart b/example/web/main.dart deleted file mode 100644 index 243cf917..00000000 --- a/example/web/main.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:angular/angular.dart'; -import 'package:chopper/chopper.dart'; -import 'package:chopper_example/angular_example.dart'; -import 'package:http/http.dart' as http; -import 'package:http/browser_client.dart'; - -// ignore: uri_has_not_been_generated -import 'main.template.dart' as ng; - -ChopperClient chopperClientFactory(http.Client httpClient) => ChopperClient( - converter: JsonConverter(), - baseUrl: 'http://localhost:9000', - client: httpClient, - ); - -@GenerateInjector([ - ClassProvider(http.Client, useClass: BrowserClient), - FactoryProvider(ChopperClient, chopperClientFactory), -]) -final InjectorFactory chopperApp = ng.chopperApp$Injector; - -ComponentRef _app; - -void main() { - _app = runApp( - appFactory, - createInjector: chopperApp, - ); -} - -Object hot$onDestroy() { - _app.destroy(); - return null; -} From 976d4573ecf7fa636569117f72459109c7314b14 Mon Sep 17 00:00:00 2001 From: ibadin Date: Sun, 24 Apr 2022 21:10:29 +0500 Subject: [PATCH 18/61] Resolve problem in main_json_serializable example (#328) --- example/bin/main_json_serializable.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index eb7fccfc..0d09e8f8 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -60,12 +60,12 @@ Future authHeader(Request request) async => applyHeader( "42", ); -typedef T JsonFactory(Map json); +typedef JsonFactory = T Function(Map json); class JsonSerializableConverter extends JsonConverter { final Map factories; - JsonSerializableConverter(this.factories); + const JsonSerializableConverter(this.factories); T? _decodeMap(Map values) { /// Get jsonFactory using Type parameters @@ -82,10 +82,10 @@ class JsonSerializableConverter extends JsonConverter { List _decodeList(Iterable values) => values.where((v) => v != null).map((v) => _decode(v)).toList(); - dynamic _decode(dynamic entity) { - if (entity is Iterable) return _decodeList(entity); + dynamic _decode(entity) { + if (entity is Iterable) return _decodeList(entity as List); - if (entity is Map) return _decodeMap(entity); + if (entity is Map) return _decodeMap(entity as Map); return entity; } From cc2da20861ebef4ef636d1a83180ece999d3d920 Mon Sep 17 00:00:00 2001 From: Meysam Karimi <31154534+meysam1717@users.noreply.github.com> Date: Mon, 2 May 2022 00:45:03 +1000 Subject: [PATCH 19/61] Add @FiledMap @PartMap @PartFileMap (#335) Co-authored-by: Meysam Karimi --- chopper/lib/src/annotations.dart | 38 ++++++++++++++++++ chopper_generator/lib/src/generator.dart | 50 +++++++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index dfc5f96b..1bd3389d 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -342,6 +342,18 @@ class Field { const Field([this.name]); } +/// Provides field parameters of a request as [Map]. +/// +/// ```dart +/// @Post(path: '/something') +/// Future fetch(@FieldMap List> query); +/// ``` +/// +@immutable +class FieldMap { + const FieldMap(); +} + /// Defines a multipart request. /// /// ```dart @@ -368,6 +380,19 @@ class Part { const Part([this.name]); } +/// Provides part parameters of a request as [PartValue]. +/// +/// ```dart +/// @Post(path: '/something') +/// @Multipart +/// Future fetch(@PartMap() List query); +/// ``` +/// +@immutable +class PartMap { + const PartMap(); +} + /// Use [PartFile] to define a file field for a [Multipart] request. /// /// ``` @@ -387,5 +412,18 @@ class PartFile { const PartFile([this.name]); } +/// Provides partFile parameters of a request as [PartValueFile]. +/// +/// ```dart +/// @Post(path: '/something') +/// @Multipart +/// Future fetch(@PartFileMap() List query); +/// ``` +/// +@immutable +class PartFileMap { + const PartFileMap(); +} + const multipart = Multipart(); const body = Body(); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index ccf42daf..a3ae9880 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -119,8 +119,11 @@ class ChopperGenerator extends GeneratorForAnnotation { final queries = _getAnnotations(m, chopper.Query); final queryMap = _getAnnotation(m, chopper.QueryMap); final fields = _getAnnotations(m, chopper.Field); + final fieldMap = _getAnnotation(m, chopper.FieldMap); final parts = _getAnnotations(m, chopper.Part); + final partMap = _getAnnotation(m, chopper.PartMap); final fileFields = _getAnnotations(m, chopper.PartFile); + final fileFieldMap = _getAnnotation(m, chopper.PartFileMap); final headers = _generateHeaders(m, method!); final url = _generateUrl(method, paths, baseUrl); @@ -190,7 +193,7 @@ class ChopperGenerator extends GeneratorForAnnotation { final methodOptionalBody = getMethodOptionalBody(method); final methodName = getMethodName(method); final methodUrl = getMethodPath(method); - final hasBody = body.isNotEmpty || fields.isNotEmpty; + var hasBody = body.isNotEmpty || fields.isNotEmpty; if (hasBody) { if (body.isNotEmpty) { blocks.add( @@ -203,13 +206,56 @@ class ChopperGenerator extends GeneratorForAnnotation { } } - final hasParts = + final hasFieldMap = fieldMap.isNotEmpty; + if (hasFieldMap) { + if (hasBody) { + blocks.add(refer('$_bodyVar.addAll').call( + [refer(fieldMap.keys.first)], + ).statement); + } else { + blocks.add( + refer(fieldMap.keys.first).assignFinal(_bodyVar).statement, + ); + } + } + + hasBody = hasBody || hasFieldMap; + + var hasParts = multipart == true && (parts.isNotEmpty || fileFields.isNotEmpty); if (hasParts) { blocks.add( _generateList(parts, fileFields).assignFinal(_partsVar).statement); } + final hasPartMap = multipart == true && partMap.isNotEmpty; + if (hasPartMap) { + if (hasParts) { + blocks.add(refer('$_partsVar.addAll').call( + [refer(partMap.keys.first)], + ).statement); + } else { + blocks.add( + refer(partMap.keys.first).assignFinal(_partsVar).statement, + ); + } + } + + final hasFileFilesMap = multipart == true && fileFieldMap.isNotEmpty; + if (hasFileFilesMap) { + if (hasParts || hasPartMap) { + blocks.add(refer('$_partsVar.addAll').call( + [refer(fileFieldMap.keys.first)], + ).statement); + } else { + blocks.add( + refer(fileFieldMap.keys.first).assignFinal(_partsVar).statement, + ); + } + } + + hasParts = hasParts || hasPartMap || hasFileFilesMap; + if (!methodOptionalBody && !hasBody && !hasParts) { _logger.warning( '$methodName $methodUrl\n' From 659b9f81a5d3ea36e27e3075771a1dd00daba555 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Mon, 20 Jun 2022 11:58:01 +0300 Subject: [PATCH 20/61] Upgrade of analyzer (#340) --- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 41173021..cec3211d 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.0.3 + +- Analyzer dependency upgrade + ## 4.0.2 - Analyzer dependency upgrade diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index a88b8b97..74edf66b 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.2 +version: 4.0.3 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper @@ -8,7 +8,7 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - analyzer: ^3.0.0 + analyzer: ^4.1.0 build: ^2.0.0 built_collection: ^5.0.0 chopper: ^4.0.0 From e167ba6ed53de55c39f0e002a70fbca759026c62 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Thu, 14 Jul 2022 17:32:02 +0100 Subject: [PATCH 21/61] Fix nullable QueryMap fails to compile (#344) --- chopper/example/definition.chopper.dart | 4 +- chopper/test/base_test.dart | 159 +++++++++++++++++++++++ chopper/test/test_service.chopper.dart | 40 +++++- chopper/test/test_service.dart | 19 +++ chopper_generator/lib/src/generator.dart | 24 +++- 5 files changed, 237 insertions(+), 9 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index 0a26d4a8..f43a754d 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -6,7 +6,7 @@ part of 'definition.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/$id'; + final $url = '/resources/${id}'; final $request = Request('GET', $url, client.baseUrl); return client.send($request); } diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index b591168a..24487c36 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -591,6 +591,165 @@ void main() { }); }); + test('Query Map 3', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=foo&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest3( + name: 'foo', + number: 1234, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 4 without QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=foo&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 4 with QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals( + '$baseUrl/test/query_map?name=foo&number=1234&filter_1=filter_value_1', + ), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + filters: { + 'filter_1': 'filter_value_1', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test( + 'Query Map 4 with QueryMap that overwrites a previous value from Query', + () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?name=bar&number=1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest4( + name: 'foo', + number: 1234, + filters: { + 'name': 'bar', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }, + ); + + test('Query Map 5 without QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest5(); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Query Map 5 with QueryMap', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_map?filter_1=filter_value_1'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getQueryMapTest5( + filters: { + 'filter_1': 'filter_value_1', + }, + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('onRequest Stream', () async { final client = MockClient((http.Request req) async { return http.Response('ok', 200); diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index 2d03d152..f1fd6769 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -6,7 +6,7 @@ part of 'test_service.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; @@ -18,7 +18,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getTest(String id, {required String dynamicHeader}) { - final $url = '/test/get/$id'; + final $url = '/test/get/${id}'; final $headers = { 'test': dynamicHeader, }; @@ -93,6 +93,36 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> getQueryMapTest3( + {String name = '', + int? number, + Map filters = const {}}) { + final $url = '/test/query_map'; + final $params = {'name': name, 'number': number}; + $params.addAll(filters); + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + + @override + Future> getQueryMapTest4( + {String name = '', int? number, Map? filters}) { + final $url = '/test/query_map'; + final $params = {'name': name, 'number': number}; + $params.addAll(filters ?? {}); + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + + @override + Future> getQueryMapTest5({Map? filters}) { + final $url = '/test/query_map'; + final $params = filters ?? {}; + final $request = Request('GET', $url, client.baseUrl, parameters: $params); + return client.send($request); + } + @override Future> getBody(dynamic body) { final $url = '/test/get_body'; @@ -119,7 +149,7 @@ class _$HttpTestService extends HttpTestService { @override Future> putTest(String test, String data) { - final $url = '/test/put/$test'; + final $url = '/test/put/${test}'; final $body = data; final $request = Request('PUT', $url, client.baseUrl, body: $body); return client.send($request); @@ -127,7 +157,7 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { - final $url = '/test/delete/$id'; + final $url = '/test/delete/${id}'; final $headers = { 'foo': 'bar', }; @@ -138,7 +168,7 @@ class _$HttpTestService extends HttpTestService { @override Future> patchTest(String id, String data) { - final $url = '/test/patch/$id'; + final $url = '/test/patch/${id}'; final $body = data; final $request = Request('PATCH', $url, client.baseUrl, body: $body); return client.send($request); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 1c68866a..fa137441 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -48,6 +48,25 @@ abstract class HttpTestService extends ChopperService { @Query('test') bool? test, }); + @Get(path: 'query_map') + Future getQueryMapTest3({ + @Query('name') String name = '', + @Query('number') int? number, + @QueryMap() Map filters = const {}, + }); + + @Get(path: 'query_map') + Future getQueryMapTest4({ + @Query('name') String name = '', + @Query('number') int? number, + @QueryMap() Map? filters, + }); + + @Get(path: 'query_map') + Future getQueryMapTest5({ + @QueryMap() Map? filters, + }); + @Get(path: 'get_body') Future getBody(@Body() dynamic body); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index a3ae9880..ab95ee80 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -10,6 +10,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:dart_style/dart_style.dart'; import 'package:source_gen/source_gen.dart'; + // TODO(lejard_h) Code builder not null safe yet // ignore: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; @@ -171,15 +172,34 @@ class ChopperGenerator extends GeneratorForAnnotation { blocks.add(_generateMap(queries).assignFinal(_parametersVar).statement); } + // Build an iterable of all the parameters that are nullable + final optionalNullableParameters = [ + ...m.parameters.where((p) => p.isOptionalPositional), + ...m.parameters.where((p) => p.isNamed), + ].where((el) => el.type.isNullable).map((el) => el.name); + final hasQueryMap = queryMap.isNotEmpty; if (hasQueryMap) { if (queries.isNotEmpty) { blocks.add(refer('$_parametersVar.addAll').call( - [refer(queryMap.keys.first)], + [ + // Check if the parameter is nullable + optionalNullableParameters.contains(queryMap.keys.first) + ? refer(queryMap.keys.first).ifNullThen(refer('{}')) + : refer(queryMap.keys.first), + ], ).statement); } else { blocks.add( - refer(queryMap.keys.first).assignFinal(_parametersVar).statement, + // Check if the parameter is nullable + optionalNullableParameters.contains(queryMap.keys.first) + ? refer(queryMap.keys.first) + .ifNullThen(refer('{}')) + .assignFinal(_parametersVar) + .statement + : refer(queryMap.keys.first) + .assignFinal(_parametersVar) + .statement, ); } } From f9009ce87e1091ee32e075f6b48b3075c41688d2 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 7 Sep 2022 09:01:35 +0100 Subject: [PATCH 22/61] Change return type of decodeJson to FutureOr in order to be able to support compute() (#345) --- chopper/lib/src/interceptor.dart | 29 ++++++++++--------- chopper/test/converter_test.dart | 18 +++++++----- .../lib/chopper_built_value.dart | 13 +++++---- chopper_built_value/test/converter_test.dart | 24 +++++++-------- chopper_generator/pubspec.yaml | 2 +- example/bin/main_built_value.dart | 7 +++-- example/bin/main_json_serializable.dart | 9 +++--- 7 files changed, 56 insertions(+), 46 deletions(-) diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 5c89cbc3..8fb147a4 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -247,7 +247,7 @@ class JsonConverter implements Converter, ErrorConverter { return request; } - Response decodeJson(Response response) { + FutureOr decodeJson(Response response) async { final supportedContentTypes = [jsonHeaders, jsonApiHeaders]; final contentType = response.headers[contentTypeKey]; @@ -264,7 +264,7 @@ class JsonConverter implements Converter, ErrorConverter { body = utf8.decode(response.bodyBytes); } - body = _tryDecodeJson(body); + body = await tryDecodeJson(body); if (isTypeOf>()) { body = body.cast(); } else if (isTypeOf>()) { @@ -275,11 +275,12 @@ class JsonConverter implements Converter, ErrorConverter { } @override - Response convertResponse(Response response) { - return decodeJson(response) as Response; - } + FutureOr> convertResponse( + Response response) async => + (await decodeJson(response)) as Response; - dynamic _tryDecodeJson(String data) { + @protected + FutureOr tryDecodeJson(String data) { try { return json.decode(data); } catch (e) { @@ -289,14 +290,13 @@ class JsonConverter implements Converter, ErrorConverter { } @override - Response convertError(Response response) => - decodeJson(response); + FutureOr convertError( + Response response) async => + await decodeJson(response); - static Response responseFactory( - Response response, - ) { - return const JsonConverter().convertResponse(response); - } + static FutureOr> responseFactory( + Response response) => + const JsonConverter().convertResponse(response); static Request requestFactory(Request request) { return const JsonConverter().convertRequest(request); @@ -339,7 +339,8 @@ class FormUrlEncodedConverter implements Converter, ErrorConverter { } @override - Response convertResponse(Response response) => + FutureOr> convertResponse( + Response response) => response as Response; @override diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 7bc98b58..5448c472 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -67,39 +67,41 @@ void main() { group('JsonConverter', () { final jsonConverter = JsonConverter(); - test('decode String', () { + test('decode String', () async { final value = 'foo'; final res = Response(http.Response('"$value"', 200), '"$value"'); - final converted = jsonConverter.convertResponse(res); + final converted = + await jsonConverter.convertResponse(res); expect(converted.body, equals(value)); }); - test('decode List String', () { + test('decode List String', () async { final res = Response( http.Response('["foo","bar"]', 200), '["foo","bar"]', ); final converted = - jsonConverter.convertResponse, String>(res); + await jsonConverter.convertResponse, String>(res); expect(converted.body, equals(['foo', 'bar'])); }); - test('decode List int', () { + test('decode List int', () async { final res = Response(http.Response('[1,2]', 200), '[1,2]'); - final converted = jsonConverter.convertResponse, int>(res); + final converted = + await jsonConverter.convertResponse, int>(res); expect(converted.body, equals([1, 2])); }); - test('decode Map', () { + test('decode Map', () async { final res = Response( http.Response('{"foo":"bar"}', 200), '{"foo":"bar"}', ); final converted = - jsonConverter.convertResponse, String>(res); + await jsonConverter.convertResponse, String>(res); expect(converted.body, equals({'foo': 'bar'})); }); diff --git a/chopper_built_value/lib/chopper_built_value.dart b/chopper_built_value/lib/chopper_built_value.dart index 0468d0a0..47d6a173 100644 --- a/chopper_built_value/lib/chopper_built_value.dart +++ b/chopper_built_value/lib/chopper_built_value.dart @@ -1,7 +1,8 @@ -import 'package:chopper/chopper.dart'; +import 'dart:async'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; +import 'package:chopper/chopper.dart'; /// A custom [Converter] and [ErrorConverter] that handles conversion for classes /// having a serializer implementation made with the built_value package. @@ -52,15 +53,17 @@ class BuiltValueConverter implements Converter, ErrorConverter { } @override - Response convertResponse(Response response) { - final jsonResponse = jsonConverter.convertResponse(response); + FutureOr> convertResponse( + Response response) async { + final jsonResponse = await jsonConverter.convertResponse(response); final body = deserialize(jsonResponse.body); return jsonResponse.copyWith(body: body); } @override - Response convertError(Response response) { - final jsonResponse = jsonConverter.convertResponse(response); + FutureOr convertError( + Response response) async { + final jsonResponse = await jsonConverter.convertResponse(response); var body; diff --git a/chopper_built_value/test/converter_test.dart b/chopper_built_value/test/converter_test.dart index f16b4171..2766f645 100644 --- a/chopper_built_value/test/converter_test.dart +++ b/chopper_built_value/test/converter_test.dart @@ -2,8 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/standard_json_plugin.dart'; import 'package:chopper/chopper.dart'; import 'package:chopper_built_value/chopper_built_value.dart'; -import 'package:test/test.dart'; import 'package:http/http.dart' as http; +import 'package:test/test.dart'; import 'data.dart'; import 'serializers.dart'; @@ -31,31 +31,31 @@ void main() { expect(request.body, '{"\$":"DataModel","id":42,"name":"foo"}'); }); - test('convert response with wireName', () { + test('convert response with wireName', () async { final string = '{"\$":"DataModel","id":42,"name":"foo"}'; final response = Response(http.Response(string, 200), string); final convertedResponse = - converter.convertResponse(response); + await converter.convertResponse(response); expect(convertedResponse.body?.id, equals(42)); expect(convertedResponse.body?.name, equals('foo')); }); - test('convert response without wireName', () { + test('convert response without wireName', () async { final string = '{"id":42,"name":"foo"}'; final response = Response(http.Response(string, 200), string); final convertedResponse = - converter.convertResponse(response); + await converter.convertResponse(response); expect(convertedResponse.body?.id, equals(42)); expect(convertedResponse.body?.name, equals('foo')); }); - test('convert response List', () { + test('convert response List', () async { final string = '[{"id":42,"name":"foo"},{"id":25,"name":"bar"}]'; final response = Response(http.Response(string, 200), string); - final convertedResponse = - converter.convertResponse, DataModel>(response); + final convertedResponse = await converter + .convertResponse, DataModel>(response); final list = convertedResponse.body; expect(list?.first.id, equals(42)); @@ -71,19 +71,19 @@ void main() { expect(request.headers['content-type'], equals('application/json')); }); - test('convert error with wire name', () { + test('convert error with wire name', () async { final string = '{"\$":"DataModel","id":42,"name":"foo"}'; final response = Response(http.Response(string, 200), string); - final convertedResponse = converter.convertError(response); + final convertedResponse = await converter.convertError(response); expect(convertedResponse.body.id, equals(42)); expect(convertedResponse.body.name, equals('foo')); }); - test('convert error using provided type', () { + test('convert error using provided type', () async { final string = '{"message":"Error message"}'; final response = Response(http.Response(string, 200), string); - final convertedResponse = converter.convertError(response); + final convertedResponse = await converter.convertError(response); expect(convertedResponse.body.message, equals('Error message')); }); diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 74edf66b..ef2adb98 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - analyzer: ^4.1.0 + analyzer: ">=4.1.0 <4.3.0" build: ^2.0.0 built_collection: ^5.0.0 chopper: ^4.0.0 diff --git a/example/bin/main_built_value.dart b/example/bin/main_built_value.dart index 8ebaff5b..a92630ea 100644 --- a/example/bin/main_built_value.dart +++ b/example/bin/main_built_value.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; import 'package:chopper/chopper.dart'; @@ -84,9 +86,10 @@ class BuiltValueConverter extends JsonConverter { } @override - Response convertResponse(Response response) { + FutureOr> convertResponse( + Response response) async { // use [JsonConverter] to decode json - final jsonRes = super.convertResponse(response); + final jsonRes = await super.convertResponse(response); final body = _decode(jsonRes.body); return jsonRes.copyWith(body: body); } diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index 0d09e8f8..75851acd 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -91,9 +91,10 @@ class JsonSerializableConverter extends JsonConverter { } @override - Response convertResponse(Response response) { + FutureOr> convertResponse( + Response response) async { // use [JsonConverter] to decode json - final jsonRes = super.convertResponse(response); + final jsonRes = await super.convertResponse(response); return jsonRes.copyWith(body: _decode(jsonRes.body)); } @@ -102,9 +103,9 @@ class JsonSerializableConverter extends JsonConverter { // all objects should implements toJson method Request convertRequest(Request request) => super.convertRequest(request); - Response convertError(Response response) { + FutureOr convertError(Response response) async { // use [JsonConverter] to decode json - final jsonRes = super.convertError(response); + final jsonRes = await super.convertError(response); return jsonRes.copyWith( body: ResourceError.fromJsonFactory(jsonRes.body), From 5f2eb829fe13ccc11569dcaf20ddb86b62d9304a Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 7 Sep 2022 16:26:29 +0100 Subject: [PATCH 23/61] Migrate from pedantic to lints ^2.0.0 with lints/recommended.yaml (#349) --- chopper/analysis_options.yaml | 35 +- chopper/example/definition.dart | 1 + chopper/example/main.dart | 3 +- chopper/lib/chopper.dart | 2 +- chopper/lib/src/annotations.dart | 96 ++--- chopper/lib/src/authenticator.dart | 7 +- chopper/lib/src/base.dart | 82 ++-- chopper/lib/src/constants.dart | 10 +- chopper/lib/src/interceptor.dart | 94 +++-- chopper/lib/src/request.dart | 83 ++-- chopper/lib/src/response.dart | 2 +- chopper/lib/src/utils.dart | 24 +- chopper/pubspec.yaml | 5 +- chopper/test/base_test.dart | 45 ++- chopper/test/client_test.dart | 7 +- chopper/test/converter_test.dart | 19 +- chopper/test/form_test.dart | 29 +- chopper/test/interceptors_test.dart | 30 +- chopper/test/json_test.dart | 12 +- chopper/test/multipart_test.dart | 87 ++-- chopper/test/test_service.dart | 7 +- chopper_built_value/analysis_options.yaml | 35 +- .../lib/chopper_built_value.dart | 36 +- chopper_built_value/pubspec.yaml | 5 +- chopper_built_value/test/data.g.dart | 40 +- chopper_built_value/test/serializers.dart | 1 + chopper_built_value/test/serializers.g.dart | 2 +- chopper_generator/analysis_options.yaml | 36 +- chopper_generator/lib/chopper_generator.dart | 1 + chopper_generator/lib/src/generator.dart | 377 +++++++++--------- chopper_generator/pubspec.yaml | 9 +- example/analysis_options.yaml | 32 ++ example/bin/main_built_value.dart | 41 +- example/bin/main_json_serializable.dart | 28 +- example/lib/built_value_resource.dart | 25 +- example/lib/built_value_resource.g.dart | 46 ++- example/lib/built_value_serializers.dart | 3 +- example/lib/built_value_serializers.g.dart | 2 +- example/lib/json_serializable.dart | 18 +- example/pubspec.lock | 93 ++++- example/pubspec.yaml | 6 +- 41 files changed, 876 insertions(+), 640 deletions(-) create mode 100644 example/analysis_options.yaml diff --git a/chopper/analysis_options.yaml b/chopper/analysis_options.yaml index d4fcc1ad..7f5a674f 100644 --- a/chopper/analysis_options.yaml +++ b/chopper/analysis_options.yaml @@ -1 +1,34 @@ -include: package:pedantic/analysis_options.yaml \ No newline at end of file +include: package:lints/recommended.yaml + +analyzer: + exclude: + - "**.g.dart" + - "**.chopper.dart" + - "**.mocks.dart" + - "example/**" + plugins: + - dart_code_metrics + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-arguments: 4 + maximum-nesting-level: 5 + number-of-parameters: 7 + metrics-exclude: + - test/** + rules: + - newline-before-return + - no-boolean-literal-compare + - no-empty-block + - prefer-trailing-comma + - prefer-conditional-expressions + - no-equal-then-else + anti-patterns: + - long-method + - long-parameter-list + +linter: + rules: + avoid_print: true + prefer_single_quotes: true diff --git a/chopper/example/definition.dart b/chopper/example/definition.dart index 5103ca4d..ef8fcf9d 100644 --- a/chopper/example/definition.dart +++ b/chopper/example/definition.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:chopper/chopper.dart'; part 'definition.chopper.dart'; diff --git a/chopper/example/main.dart b/chopper/example/main.dart index d1831859..16f66abc 100644 --- a/chopper/example/main.dart +++ b/chopper/example/main.dart @@ -1,4 +1,5 @@ import 'package:chopper/chopper.dart'; + import 'definition.dart'; Future main() async { @@ -6,7 +7,7 @@ Future main() async { baseUrl: 'http://localhost:8000', services: [ // the generated service - MyService.create(ChopperClient()) + MyService.create(ChopperClient()), ], converter: JsonConverter(), ); diff --git a/chopper/lib/chopper.dart b/chopper/lib/chopper.dart index 9d1d9826..c004d675 100644 --- a/chopper/lib/chopper.dart +++ b/chopper/lib/chopper.dart @@ -6,8 +6,8 @@ library chopper; export 'src/annotations.dart'; export 'src/authenticator.dart'; export 'src/base.dart'; +export 'src/constants.dart'; export 'src/interceptor.dart'; export 'src/request.dart'; export 'src/response.dart'; export 'src/utils.dart' hide mapToQuery; -export 'src/constants.dart'; diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index 1bd3389d..dafed63b 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -1,8 +1,10 @@ import 'dart:async'; + import 'package:meta/meta.dart'; + +import 'constants.dart'; import 'request.dart'; import 'response.dart'; -import 'constants.dart'; /// Defines a Chopper API. /// @@ -171,15 +173,10 @@ class Method { @immutable class Get extends Method { const Get({ - bool optionalBody = true, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Get, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody = true, + super.path, + super.headers, + }) : super(HttpMethod.Get); } /// Defines a method as an HTTP POST request. @@ -188,30 +185,20 @@ class Get extends Method { @immutable class Post extends Method { const Post({ - bool optionalBody = false, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Post, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody, + super.path, + super.headers, + }) : super(HttpMethod.Post); } /// Defines a method as an HTTP DELETE request. @immutable class Delete extends Method { const Delete({ - bool optionalBody = true, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Delete, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody = true, + super.path, + super.headers, + }) : super(HttpMethod.Delete); } /// Defines a method as an HTTP PUT request. @@ -220,15 +207,10 @@ class Delete extends Method { @immutable class Put extends Method { const Put({ - bool optionalBody = false, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Put, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody, + super.path, + super.headers, + }) : super(HttpMethod.Put); } /// Defines a method as an HTTP PATCH request. @@ -236,44 +218,29 @@ class Put extends Method { @immutable class Patch extends Method { const Patch({ - bool optionalBody = false, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Patch, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody, + super.path, + super.headers, + }) : super(HttpMethod.Patch); } /// Defines a method as an HTTP HEAD request. @immutable class Head extends Method { const Head({ - bool optionalBody = true, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Head, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody = true, + super.path, + super.headers, + }) : super(HttpMethod.Head); } @immutable class Options extends Method { const Options({ - bool optionalBody = true, - String path = '', - Map headers = const {}, - }) : super( - HttpMethod.Options, - optionalBody: optionalBody, - path: path, - headers: headers, - ); + super.optionalBody = true, + super.path, + super.headers, + }) : super(HttpMethod.Options); } /// A function that should convert the body of a [Request] to the HTTP representation. @@ -377,6 +344,7 @@ class Multipart { @immutable class Part { final String? name; + const Part([this.name]); } diff --git a/chopper/lib/src/authenticator.dart b/chopper/lib/src/authenticator.dart index db225a49..d69e6d76 100644 --- a/chopper/lib/src/authenticator.dart +++ b/chopper/lib/src/authenticator.dart @@ -5,6 +5,9 @@ import 'package:chopper/chopper.dart'; /// This method should return a [Request] that includes credentials to satisfy an authentication challenge received in /// [response]. It should return `null` if the challenge cannot be satisfied. abstract class Authenticator { - FutureOr authenticate(Request request, Response response, - [Request? originalRequest]); + FutureOr authenticate( + Request request, + Response response, [ + Request? originalRequest, + ]); } diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index b0f90848..3fa4e998 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -1,19 +1,20 @@ import 'dart:async'; -import 'package:meta/meta.dart'; + import 'package:http/http.dart' as http; -import 'constants.dart'; +import 'package:meta/meta.dart'; +import 'annotations.dart'; +import 'authenticator.dart'; +import 'constants.dart'; import 'interceptor.dart'; import 'request.dart'; import 'response.dart'; -import 'annotations.dart'; -import 'authenticator.dart'; import 'utils.dart'; Type _typeOf() => T; @visibleForTesting -final allowedInterceptorsType = [ +final List allowedInterceptorsType = [ RequestInterceptor, RequestInterceptorFunc, ResponseInterceptor, @@ -120,7 +121,7 @@ class ChopperClient { Iterable services = const [], }) : httpClient = client ?? http.Client(), _clientIsInternal = client == null { - if (interceptors.every(_isAnInterceptor) == false) { + if (!interceptors.every(_isAnInterceptor)) { throw ArgumentError( 'Unsupported type for interceptors, it only support the following types:\n' '${allowedInterceptorsType.join('\n - ')}', @@ -162,31 +163,28 @@ class ChopperClient { /// final todoService = chopper.getService(); /// ``` ServiceType getService() { - final serviceType = _typeOf(); + final Type serviceType = _typeOf(); if (serviceType == dynamic || serviceType == ChopperService) { throw Exception( - 'Service type should be provided, `dynamic` is not allowed.'); + 'Service type should be provided, `dynamic` is not allowed.', + ); } - final service = _services[serviceType]; + final ChopperService? service = _services[serviceType]; if (service == null) { throw Exception('Service of type \'$serviceType\' not found.'); } + return service as ServiceType; } - Future _encodeRequest(Request request) async { - return converter?.convertRequest(request) ?? request; - } + Future _encodeRequest(Request request) async => + converter?.convertRequest(request) ?? request; Future> _decodeResponse( Response response, Converter withConverter, - ) async { - final converted = - await withConverter.convertResponse(response); - - return converted; - } + ) async => + await withConverter.convertResponse(response); Future _interceptRequest(Request req) async { final body = req.body; @@ -203,6 +201,7 @@ class ChopperClient { 'Interceptors should not transform the body of the request' 'Use Request converter instead', ); + return req; } @@ -242,11 +241,7 @@ class ChopperClient { error = errorRes?.error ?? errorRes?.body; } - return Response( - response.base, - null, - error: error, - ); + return Response(response.base, null, error: error); } Future> _handleSuccessResponse( @@ -269,17 +264,12 @@ class ChopperClient { Future _handleRequestConverter( Request request, ConvertRequest? requestConverter, - ) async { - if (request.body != null || request.parts.isNotEmpty) { - if (requestConverter != null) { - request = await requestConverter(request); - } else { - request = (await _encodeRequest(request)); - } - } - - return request; - } + ) async => + request.body != null || request.parts.isNotEmpty + ? requestConverter != null + ? await requestConverter(request) + : await _encodeRequest(request) + : request; /// Sends a pre-build [Request], applying all provided [Interceptor]s and /// [Converter]s. @@ -298,8 +288,9 @@ class ChopperClient { ConvertRequest? requestConverter, ConvertResponse? responseConverter, }) async { - var req = await _handleRequestConverter(request, requestConverter); - req = await _interceptRequest(req); + var req = await _interceptRequest( + await _handleRequestConverter(request, requestConverter), + ); _requestController.add(req); final streamRes = await httpClient.send(await req.toBaseRequest()); @@ -324,27 +315,28 @@ class ChopperClient { return _processResponse(res); } else { res = await _handleErrorResponse(res); + return _processResponse(res); } } } - if (_responseIsSuccessful(res.statusCode)) { - res = await _handleSuccessResponse( - res, - responseConverter, - ); - } else { - res = await _handleErrorResponse(res); - } + res = _responseIsSuccessful(res.statusCode) + ? await _handleSuccessResponse( + res, + responseConverter, + ) + : await _handleErrorResponse(res); return _processResponse(res); } Future> _processResponse( - dynamic res) async { + dynamic res, + ) async { res = await _interceptResponse(res); _responseController.add(res); + return res; } diff --git a/chopper/lib/src/constants.dart b/chopper/lib/src/constants.dart index 21f388aa..e7e8faec 100644 --- a/chopper/lib/src/constants.dart +++ b/chopper/lib/src/constants.dart @@ -1,9 +1,11 @@ -const contentTypeKey = 'content-type'; -const jsonHeaders = 'application/json'; -const formEncodedHeaders = 'application/x-www-form-urlencoded'; +// ignore_for_file: constant_identifier_names + +const String contentTypeKey = 'content-type'; +const String jsonHeaders = 'application/json'; +const String formEncodedHeaders = 'application/x-www-form-urlencoded'; // Represent the header for a json api response https://jsonapi.org/#mime-types -const jsonApiHeaders = 'application/vnd.api+json'; +const String jsonApiHeaders = 'application/vnd.api+json'; class HttpMethod { static const String Get = 'GET'; diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 8fb147a4..9d393afc 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:convert'; -import 'package:meta/meta.dart'; + import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; +import 'constants.dart'; import 'request.dart'; import 'response.dart'; import 'utils.dart'; -import 'constants.dart'; /// An interface for implementing response interceptors. /// @@ -136,14 +137,11 @@ typedef RequestInterceptorFunc = FutureOr Function(Request request); class CurlInterceptor implements RequestInterceptor { @override Future onRequest(Request request) async { - final baseRequest = await request.toBaseRequest(); - final method = baseRequest.method; - final url = baseRequest.url.toString(); - final headers = baseRequest.headers; - var curl = ''; - curl += 'curl'; - curl += ' -v'; - curl += ' -X $method'; + final http.BaseRequest baseRequest = await request.toBaseRequest(); + final String method = baseRequest.method; + final String url = baseRequest.url.toString(); + final Map headers = baseRequest.headers; + String curl = 'curl -v -X $method'; headers.forEach((k, v) { curl += ' -H \'$k: $v\''; }); @@ -154,8 +152,9 @@ class CurlInterceptor implements RequestInterceptor { curl += ' -d \'$body\''; } } - curl += ' \"$url\"'; + curl += ' "$url"'; chopperLogger.info(curl); + return request; } } @@ -172,11 +171,11 @@ class HttpLoggingInterceptor implements RequestInterceptor, ResponseInterceptor { @override FutureOr onRequest(Request request) async { - final base = await request.toBaseRequest(); + final http.BaseRequest base = await request.toBaseRequest(); chopperLogger.info('--> ${base.method} ${base.url}'); base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); - var bytes = ''; + String bytes = ''; if (base is http.Request) { final body = base.body; if (body.isNotEmpty) { @@ -186,17 +185,18 @@ class HttpLoggingInterceptor } chopperLogger.info('--> END ${base.method}$bytes'); + return request; } @override FutureOr onResponse(Response response) { - final base = response.base.request; + final http.BaseRequest? base = response.base.request; chopperLogger.info('<-- ${response.statusCode} ${base!.url}'); response.base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); - var bytes; + String bytes = ''; if (response.base is http.Response) { final resp = response.base as http.Response; if (resp.body.isNotEmpty) { @@ -206,6 +206,7 @@ class HttpLoggingInterceptor } chopperLogger.info('--> END ${base.method}$bytes'); + return response; } } @@ -228,29 +229,27 @@ class JsonConverter implements Converter, ErrorConverter { const JsonConverter(); @override - Request convertRequest(Request request) { - final req = applyHeader( - request, - contentTypeKey, - jsonHeaders, - override: false, - ); - - return encodeJson(req); - } + Request convertRequest(Request request) => encodeJson( + applyHeader( + request, + contentTypeKey, + jsonHeaders, + override: false, + ), + ); Request encodeJson(Request request) { - var contentType = request.headers[contentTypeKey]; - if (contentType != null && contentType.contains(jsonHeaders)) { - return request.copyWith(body: json.encode(request.body)); - } - return request; + final String? contentType = request.headers[contentTypeKey]; + + return (contentType?.contains(jsonHeaders) ?? false) + ? request.copyWith(body: json.encode(request.body)) + : request; } FutureOr decodeJson(Response response) async { - final supportedContentTypes = [jsonHeaders, jsonApiHeaders]; + final List supportedContentTypes = [jsonHeaders, jsonApiHeaders]; - final contentType = response.headers[contentTypeKey]; + final String? contentType = response.headers[contentTypeKey]; var body = response.body; if (supportedContentTypes.contains(contentType)) { @@ -276,7 +275,8 @@ class JsonConverter implements Converter, ErrorConverter { @override FutureOr> convertResponse( - Response response) async => + Response response, + ) async => (await decodeJson(response)) as Response; @protected @@ -285,22 +285,24 @@ class JsonConverter implements Converter, ErrorConverter { return json.decode(data); } catch (e) { chopperLogger.warning(e); + return data; } } @override FutureOr convertError( - Response response) async => + Response response, + ) async => await decodeJson(response); static FutureOr> responseFactory( - Response response) => + Response response, + ) => const JsonConverter().convertResponse(response); - static Request requestFactory(Request request) { - return const JsonConverter().convertRequest(request); - } + static Request requestFactory(Request request) => + const JsonConverter().convertRequest(request); } /// A [Converter] implementation that converts only [Request]s having a [Map] as their body. @@ -314,7 +316,7 @@ class FormUrlEncodedConverter implements Converter, ErrorConverter { @override Request convertRequest(Request request) { - var req = applyHeader( + final Request req = applyHeader( request, contentTypeKey, formEncodedHeaders, @@ -324,15 +326,10 @@ class FormUrlEncodedConverter implements Converter, ErrorConverter { if (req.body is Map) return req; if (req.body is Map) { - final body = {}; - - req.body.forEach((key, val) { - if (val != null) { - body[key.toString()] = val.toString(); - } + return req.copyWith(body: { + for (final MapEntry e in req.body.entries) + if (e.value != null) e.key.toString(): e.value.toString(), }); - - req = req.copyWith(body: body); } return req; @@ -340,7 +337,8 @@ class FormUrlEncodedConverter implements Converter, ErrorConverter { @override FutureOr> convertResponse( - Response response) => + Response response, + ) => response as Response; @override diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index 88a7ed05..8378e276 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:convert'; -import 'package:meta/meta.dart'; import 'package:http/http.dart' as http; -import 'utils.dart'; +import 'package:meta/meta.dart'; + import 'constants.dart'; +import 'utils.dart'; /// This class represents an HTTP request that can be made with Chopper. @immutable @@ -54,7 +55,7 @@ class Request { Uri _buildUri() => buildUri(baseUrl, url, parameters); - Map _buildHeaders() => Map.from(headers); + Map _buildHeaders() => {...headers}; /// Converts this Chopper Request into a [http.BaseRequest]. /// @@ -65,32 +66,18 @@ class Request { /// - [http.MultipartRequest] if [multipart] is true /// - or a [http.Request] Future toBaseRequest() async { - final uri = _buildUri(); - final heads = _buildHeaders(); + final Uri uri = _buildUri(); + final Map heads = _buildHeaders(); if (body is Stream>) { - return toStreamedRequest( - body, - method, - uri, - heads, - ); + return toStreamedRequest(body, method, uri, heads); } if (multipart) { - return toMultipartRequest( - parts, - method, - uri, - heads, - ); + return toMultipartRequest(parts, method, uri, heads); } - return toHttpRequest( - body, - method, - uri, - heads, - ); + + return toHttpRequest(body, method, uri, heads); } } @@ -117,33 +104,30 @@ class PartValue { /// Represents a file part in a multipart request. @immutable class PartValueFile extends PartValue { - PartValueFile(String name, T value) : super(name, value); + const PartValueFile(super.name, super.value); } /// Builds a valid URI from [baseUrl], [url] and [parameters]. /// /// If [url] starts with 'http://' or 'https://', baseUrl is ignored. Uri buildUri(String baseUrl, String url, Map parameters) { - var uri; - if (url.startsWith('http://') || url.startsWith('https://')) { - // If the request's url is already a fully qualified URL, we can use it - // as-is and ignore the baseUrl. - uri = Uri.parse(url); - } else { - if (!baseUrl.endsWith('/') && !url.startsWith('/')) { - uri = Uri.parse('$baseUrl/$url'); - } else { - uri = Uri.parse('$baseUrl$url'); - } - } - - var query = mapToQuery(parameters); + // If the request's url is already a fully qualified URL, we can use it + // as-is and ignore the baseUrl. + Uri uri = url.startsWith('http://') || url.startsWith('https://') + ? Uri.parse(url) + : !baseUrl.endsWith('/') && !url.startsWith('/') + ? Uri.parse('$baseUrl/$url') + : Uri.parse('$baseUrl$url'); + + String query = mapToQuery(parameters); if (query.isNotEmpty) { if (uri.hasQuery) { query += '&${uri.query}'; } + return uri.replace(query: query); } + return uri; } @@ -154,8 +138,8 @@ Future toHttpRequest( Uri uri, Map headers, ) async { - final baseRequest = http.Request(method, uri); - baseRequest.headers.addAll(headers); + final http.Request baseRequest = http.Request(method, uri) + ..headers.addAll(headers); if (body != null) { if (body is String) { @@ -168,6 +152,7 @@ Future toHttpRequest( throw ArgumentError.value('$body', 'body'); } } + return baseRequest; } @@ -178,10 +163,10 @@ Future toMultipartRequest( Uri uri, Map headers, ) async { - final baseRequest = http.MultipartRequest(method, uri); - baseRequest.headers.addAll(headers); + final http.MultipartRequest baseRequest = http.MultipartRequest(method, uri) + ..headers.addAll(headers); - for (final part in parts) { + for (final PartValue part in parts) { if (part.value == null) continue; if (part.value is http.MultipartFile) { @@ -210,6 +195,7 @@ Future toMultipartRequest( baseRequest.fields[part.name] = part.value.toString(); } } + return baseRequest; } @@ -220,11 +206,14 @@ Future toStreamedRequest( Uri uri, Map headers, ) async { - final req = http.StreamedRequest(method, uri); - req.headers.addAll(headers); + final http.StreamedRequest req = http.StreamedRequest(method, uri) + ..headers.addAll(headers); - bodyStream.listen(req.sink.add, - onDone: req.sink.close, onError: req.sink.addError); + bodyStream.listen( + req.sink.add, + onDone: req.sink.close, + onError: req.sink.addError, + ); return req; } diff --git a/chopper/lib/src/response.dart b/chopper/lib/src/response.dart index e4d9a56f..1fc29fac 100644 --- a/chopper/lib/src/response.dart +++ b/chopper/lib/src/response.dart @@ -29,7 +29,7 @@ class Response { /// The body of the response if [isSuccessful] is false. final Object? error; - Response(this.base, this.body, {this.error}); + const Response(this.base, this.body, {this.error}); /// Makes a copy of this Response, replacing original values with the given ones. /// This method can also alter the type of the response body. diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index 21b0605f..e4d17f7d 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -39,16 +39,16 @@ Request applyHeaders( Map headers, { bool override = true, }) { - final h = Map.from(request.headers); + final Map headersCopy = {...request.headers}; - for (var k in headers.keys) { - var val = headers[k]; - if (val == null) continue; - if (!override && h.containsKey(k)) continue; - h[k] = val; + for (String key in headers.keys) { + String? value = headers[key]; + if (value == null) continue; + if (!override && headersCopy.containsKey(key)) continue; + headersCopy[key] = value; } - return request.copyWith(headers: h); + return request.copyWith(headers: headersCopy); } final chopperLogger = Logger('Chopper'); @@ -62,12 +62,11 @@ Iterable<_Pair> _mapToQuery( Map map, { String? prefix, }) { - /// ignore: prefer_collection_literals - final pairs = Set<_Pair>(); + final Set<_Pair> pairs = {}; map.forEach((key, value) { if (value != null) { - var name = Uri.encodeQueryComponent(key); + String name = Uri.encodeQueryComponent(key); if (prefix != null) { name = '$prefix.$name'; @@ -77,11 +76,12 @@ Iterable<_Pair> _mapToQuery( pairs.addAll(_iterableToQuery(name, value)); } else if (value is Map) { pairs.addAll(_mapToQuery(value, prefix: name)); - } else if (value.toString().isNotEmpty == true) { + } else if (value.toString().isNotEmpty) { pairs.add(_Pair(name, _normalizeValue(value))); } } }); + return pairs; } @@ -97,7 +97,7 @@ class _Pair { final A first; final B second; - _Pair(this.first, this.second); + const _Pair(this.first, this.second); @override String toString() => '$first=$second'; diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index d2e41a97..cff2ec03 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -5,7 +5,7 @@ documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: http: ">=0.13.0 <1.0.0" @@ -18,6 +18,7 @@ dev_dependencies: build_test: ^2.0.0 coverage: ^1.0.2 http_parser: ^4.0.0 - pedantic: ^1.11.0 + dart_code_metrics: ^4.8.1 + lints: ^2.0.0 chopper_generator: path: ../chopper_generator diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 24487c36..99d81231 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -2,16 +2,19 @@ import 'dart:async'; import 'dart:convert'; import 'package:chopper/chopper.dart'; -import 'package:test/test.dart'; -import 'package:http/testing.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + import 'test_service.dart'; const baseUrl = 'http://localhost:8000'; void main() { - final buildClient = ( - [http.Client? httpClient, ErrorConverter? errorConverter]) => + ChopperClient buildClient([ + http.Client? httpClient, + ErrorConverter? errorConverter, + ]) => ChopperClient( baseUrl: baseUrl, services: [ @@ -21,6 +24,7 @@ void main() { client: httpClient, errorConverter: errorConverter, ); + group('Base', () { test('get service errors', () async { final chopper = ChopperClient( @@ -39,8 +43,10 @@ void main() { try { chopper.getService(); } on Exception catch (e) { - expect(e.toString(), - 'Exception: Service type should be provided, `dynamic` is not allowed.'); + expect( + e.toString(), + 'Exception: Service type should be provided, `dynamic` is not allowed.', + ); } }); test('GET', () async { @@ -332,6 +338,7 @@ void main() { final client = MockClient((http.Request req) async { expect(req.headers.containsKey('foo'), isTrue); expect(req.headers['foo'], equals('bar')); + return http.Response('', 200); }); @@ -351,6 +358,7 @@ void main() { final client = MockClient((http.Request req) async { expect(req.headers.containsKey('test'), isTrue); expect(req.headers['test'], equals('42')); + return http.Response('', 200); }); @@ -375,6 +383,7 @@ void main() { req.url.toString(), equals('$baseUrl/test/get/1234'), ); + return http.Response('', 200); }); @@ -392,13 +401,10 @@ void main() { test('applyHeader', () { final req1 = applyHeader( - Request( - 'GET', - '/', - baseUrl, - ), - 'foo', - 'bar'); + Request('GET', '/', baseUrl), + 'foo', + 'bar', + ); expect(req1.headers, equals({'foo': 'bar'})); @@ -565,7 +571,8 @@ void main() { expect( request.url.toString(), equals( - '$baseUrl/test/query_map?test=true&foo=bar&list=1&list=2&inner.test=42'), + '$baseUrl/test/query_map?test=true&foo=bar&list=1&list=2&inner.test=42', + ), ); expect(request.method, equals('GET')); @@ -841,7 +848,7 @@ void main() { final chopper = buildClient(httpClient); final service = chopper.getService(); - final _ = await service.getAll(); + await service.getAll(); }); test('Slash in path gives a trailing slash', () async { @@ -858,12 +865,13 @@ void main() { final chopper = buildClient(httpClient); final service = chopper.getService(); - final _ = await service.getAllWithTrailingSlash(); + await service.getAllWithTrailingSlash(); }); test('timeout', () async { final httpClient = MockClient((http.Request req) async { await Future.delayed(const Duration(minutes: 1)); + return http.Response('ok', 200); }); @@ -872,10 +880,7 @@ void main() { try { await service - .getTest( - '1234', - dynamicHeader: '', - ) + .getTest('1234', dynamicHeader: '') .timeout(const Duration(seconds: 3)); } catch (e) { expect(e is TimeoutException, isTrue); diff --git a/chopper/test/client_test.dart b/chopper/test/client_test.dart index 81c6d0c8..babb172d 100644 --- a/chopper/test/client_test.dart +++ b/chopper/test/client_test.dart @@ -1,14 +1,14 @@ import 'dart:convert'; import 'package:chopper/chopper.dart'; -import 'package:test/test.dart'; -import 'package:http/testing.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; const baseUrl = 'http://localhost:8000'; void main() { - final buildClient = ([http.Client? httpClient]) => ChopperClient( + ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( baseUrl: baseUrl, client: httpClient, interceptors: [ @@ -16,6 +16,7 @@ void main() { ], converter: JsonConverter(), ); + group('Client methods', () { test('GET', () async { final httpClient = MockClient((request) async { diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 5448c472..2fae6736 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -1,16 +1,17 @@ import 'dart:convert' as dart_convert; import 'package:chopper/chopper.dart'; -import 'package:test/test.dart'; -import 'package:http/testing.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + import 'test_service.dart'; const baseUrl = 'http://localhost:8000'; void main() { group('Converter', () { - final buildClient = (http.BaseClient client) => ChopperClient( + ChopperClient buildClient(http.BaseClient client) => ChopperClient( baseUrl: baseUrl, client: client, converter: TestConverter(), @@ -113,16 +114,16 @@ class TestConverter implements Converter { Response convertResponse(Response res) { if (res.body is String) { return res.copyWith<_Converted>( - body: _Converted(res.body)) as Response; + body: _Converted(res.body), + ) as Response; } + return res as Response; } @override - Request convertRequest(Request req) { - if (req.body is _Converted) return req.copyWith(body: req.body.data); - return req; - } + Request convertRequest(Request req) => + req.body is _Converted ? req.copyWith(body: req.body.data) : req; } class TestErrorConverter implements ErrorConverter { @@ -130,8 +131,10 @@ class TestErrorConverter implements ErrorConverter { Response convertError(Response res) { if (res.body is String) { final error = dart_convert.jsonDecode(res.body); + return res.copyWith<_ConvertedError>(body: _ConvertedError(error)); } + return res; } } diff --git a/chopper/test/form_test.dart b/chopper/test/form_test.dart index 95045d0f..ef5859d1 100644 --- a/chopper/test/form_test.dart +++ b/chopper/test/form_test.dart @@ -1,20 +1,21 @@ -import 'package:test/test.dart'; import 'package:chopper/chopper.dart'; -import 'test_service.dart'; -import 'package:http/testing.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'test_service.dart'; void main() { group('Form', () { - final buildClient = - (http.Client httpClient, {bool isJson = false}) => ChopperClient( - services: [ - // the generated service - HttpTestService.create(), - ], - client: httpClient, - converter: isJson ? JsonConverter() : null, - ); + ChopperClient buildClient(http.Client httpClient, {bool isJson = false}) => + ChopperClient( + services: [ + // the generated service + HttpTestService.create(), + ], + client: httpClient, + converter: isJson ? JsonConverter() : null, + ); test('form-urlencoded default if no converter', () async { final httpClient = MockClient((http.Request req) async { @@ -24,6 +25,7 @@ void main() { 'application/x-www-form-urlencoded; charset=utf-8', ); expect(req.body, 'foo=test&default=hello'); + return http.Response('ok', 200); }); @@ -46,6 +48,7 @@ void main() { 'application/x-www-form-urlencoded; charset=utf-8', ); expect(req.body, 'foo=test&factory=converter'); + return http.Response('ok', 200); }); @@ -68,6 +71,7 @@ void main() { 'application/x-www-form-urlencoded; charset=utf-8', ); expect(req.body, 'foo=test&factory=converter'); + return http.Response('ok', 200); }); @@ -91,6 +95,7 @@ void main() { 'application/x-www-form-urlencoded; charset=utf-8', ); expect(req.body, 'foo=test&bar=42'); + return http.Response('ok', 200); }); diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index a325faeb..4650d89c 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -1,9 +1,10 @@ import 'dart:async'; -import 'package:http/testing.dart'; +import 'package:chopper/chopper.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; import 'package:test/test.dart'; -import 'package:chopper/chopper.dart'; + import 'test_service.dart'; void main() { @@ -14,6 +15,7 @@ void main() { request.url.toString(), equals('/test/get/1234/intercept'), ); + return http.Response('', 200); }, ); @@ -78,12 +80,13 @@ void main() { }); test('ResponseInterceptorFunc', () async { - var intercepted; + dynamic intercepted; final chopper = ChopperClient( interceptors: [ (Response response) { intercepted = _Intercepted(response.body); + return response; }, ], @@ -102,12 +105,13 @@ void main() { }); test('TypedResponseInterceptorFunc1', () async { - var intercepted; + dynamic intercepted; final chopper = ChopperClient( interceptors: [ (Response response) { intercepted = _Intercepted(response.body); + return response; }, ], @@ -130,7 +134,7 @@ void main() { return http.Response('["1","2"]', 200); }); - var intercepted; + dynamic intercepted; final chopper = ChopperClient( client: client, @@ -139,7 +143,8 @@ void main() { (Response response) { expect(isTypeOf(), isTrue); expect(isTypeOf>(), isTrue); - intercepted = _Intercepted(response.body!); + intercepted = _Intercepted(response.body as BodyType); + return response; }, ], @@ -157,12 +162,13 @@ void main() { final client = MockClient((http.Request req) async { expect(req.headers.containsKey('foo'), isTrue); expect(req.headers['foo'], equals('bar')); + return http.Response('', 200); }); final chopper = ChopperClient( interceptors: [ - HeadersInterceptor({'foo': 'bar'}) + HeadersInterceptor({'foo': 'bar'}), ], services: [ HttpTestService.create(), @@ -223,9 +229,12 @@ void main() { final logger = HttpLoggingInterceptor(); final fakeResponse = Response( - http.Response('responseBodyBase', 200, - headers: {'foo': 'bar'}, - request: await fakeRequest.toBaseRequest()), + http.Response( + 'responseBodyBase', + 200, + headers: {'foo': 'bar'}, + request: await fakeRequest.toBaseRequest(), + ), 'responseBody', ); @@ -254,6 +263,7 @@ class ResponseIntercept implements ResponseInterceptor { @override FutureOr onResponse(Response response) { intercepted = _Intercepted(response.body); + return response; } } diff --git a/chopper/test/json_test.dart b/chopper/test/json_test.dart index c65053f0..6a8c637f 100644 --- a/chopper/test/json_test.dart +++ b/chopper/test/json_test.dart @@ -1,10 +1,11 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:chopper/chopper.dart'; -import 'test_service.dart'; -import 'package:http/testing.dart'; import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'test_service.dart'; void main() { final sample = { @@ -15,7 +16,8 @@ void main() { 'result': 'ok', }; group('JSON', () { - final buildClient = (bool json, http.Client httpClient) => ChopperClient( + ChopperClient buildClient(bool json, http.Client httpClient) => + ChopperClient( services: [ // the generated service HttpTestService.create(), @@ -30,6 +32,7 @@ void main() { expect(req.url.toString(), equals('/test/map')); expect(req.headers['content-type'], 'application/json; charset=utf-8'); expect(req.body, equals(json.encode(sample))); + return http.Response( json.encode(res), 200, @@ -56,6 +59,7 @@ void main() { expect(req.headers['content-type'], 'application/json; charset=utf-8'); expect(req.headers['customConverter'], 'true'); expect(req.body, equals(json.encode(sample))); + return http.Response( json.encode(res), 200, diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index ca42cd95..20d28044 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -1,8 +1,9 @@ import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; import 'package:http_parser/http_parser.dart'; import 'package:test/test.dart'; -import 'package:http/testing.dart'; -import 'package:http/http.dart' as http; + import 'test_service.dart'; void main() { @@ -26,6 +27,7 @@ void main() { '{bar: foo}\r\n', ), ); + return http.Response('ok', 200); }); @@ -46,14 +48,14 @@ void main() { contains('content-type: application/octet-stream'), ); expect( - req.body, - contains( - 'content-disposition: form-data; name="file"', - )); + req.body, + contains('content-disposition: form-data; name="file"'), + ); expect( req.body, - contains('${String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])}'), + contains(String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), ); + return http.Response('ok', 200); }); @@ -79,14 +81,16 @@ void main() { isNot(contains('content-disposition: form-data; name="id"')), ); expect( - req.body, - contains( - 'content-disposition: form-data; name="file_field"; filename="file_name"', - )); + req.body, + contains( + 'content-disposition: form-data; name="file_field"; filename="file_name"', + ), + ); expect( req.body, - contains('${String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])}'), + contains(String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), ); + return http.Response('ok', 200); }); @@ -109,22 +113,28 @@ void main() { final httpClient = MockClient((http.Request req) async { expect(req.headers['Content-Type'], contains('multipart/form-data;')); - expect(req.body, - contains('content-disposition: form-data; name="id"\r\n\r\n42\r\n')); + expect( + req.body, + contains( + 'content-disposition: form-data; name="id"\r\n\r\n42\r\n', + ), + ); expect( req.body, contains('content-type: application/octet-stream'), ); expect( - req.body, - contains( - 'content-disposition: form-data; name="file_field"; filename="file_name"', - )); + req.body, + contains( + 'content-disposition: form-data; name="file_field"; filename="file_name"', + ), + ); expect( req.body, - contains('${String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])}'), + contains(String.fromCharCodes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])), ); + return http.Response('ok', 200); }); @@ -166,6 +176,7 @@ void main() { 'World', ), ); + return http.Response('ok', 200); }); @@ -206,23 +217,29 @@ void main() { expect(req.fields['int'], equals('42')); }); - test('PartFile', () async { - final req = await toMultipartRequest( - [ - PartValueFile('foo', 'test/multipart_test.dart'), - PartValueFile>('int', [1, 2]), - ], - HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + test( + 'PartFile', + () async { + final req = await toMultipartRequest( + [ + PartValueFile('foo', 'test/multipart_test.dart'), + PartValueFile>('int', [1, 2]), + ], + HttpMethod.Post, + Uri.parse('/foo'), + {}, + ); - expect(req.files.firstWhere((f) => f.field == 'foo').filename, - equals('multipart_test.dart')); - final bytes = - await req.files.firstWhere((f) => f.field == 'int').finalize().first; - expect(bytes, equals([1, 2])); - }, testOn: 'vm'); + expect( + req.files.firstWhere((f) => f.field == 'foo').filename, + equals('multipart_test.dart'), + ); + final bytes = + await req.files.firstWhere((f) => f.field == 'int').finalize().first; + expect(bytes, equals([1, 2])); + }, + testOn: 'vm', + ); test('PartValue.replace', () { dynamic part = PartValue('foo', 'bar'); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index fa137441..03abc239 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:chopper/chopper.dart'; +import 'package:chopper/chopper.dart'; import 'package:http/http.dart' show MultipartFile; part 'test_service.chopper.dart'; @@ -101,7 +101,9 @@ abstract class HttpTestService extends ChopperService { @Post(path: 'map/json') @FactoryConverter( - request: customConvertRequest, response: customConvertResponse) + request: customConvertRequest, + response: customConvertResponse, + ) Future forceJsonTest(@Body() Map map); @Post(path: 'multi') @@ -140,6 +142,7 @@ abstract class HttpTestService extends ChopperService { Request customConvertRequest(Request req) { final r = JsonConverter().convertRequest(req); + return applyHeader(r, 'customConverter', 'true'); } diff --git a/chopper_built_value/analysis_options.yaml b/chopper_built_value/analysis_options.yaml index d4fcc1ad..7f5a674f 100644 --- a/chopper_built_value/analysis_options.yaml +++ b/chopper_built_value/analysis_options.yaml @@ -1 +1,34 @@ -include: package:pedantic/analysis_options.yaml \ No newline at end of file +include: package:lints/recommended.yaml + +analyzer: + exclude: + - "**.g.dart" + - "**.chopper.dart" + - "**.mocks.dart" + - "example/**" + plugins: + - dart_code_metrics + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-arguments: 4 + maximum-nesting-level: 5 + number-of-parameters: 7 + metrics-exclude: + - test/** + rules: + - newline-before-return + - no-boolean-literal-compare + - no-empty-block + - prefer-trailing-comma + - prefer-conditional-expressions + - no-equal-then-else + anti-patterns: + - long-method + - long-parameter-list + +linter: + rules: + avoid_print: true + prefer_single_quotes: true diff --git a/chopper_built_value/lib/chopper_built_value.dart b/chopper_built_value/lib/chopper_built_value.dart index 47d6a173..7beef449 100644 --- a/chopper_built_value/lib/chopper_built_value.dart +++ b/chopper_built_value/lib/chopper_built_value.dart @@ -8,7 +8,7 @@ import 'package:chopper/chopper.dart'; /// having a serializer implementation made with the built_value package. class BuiltValueConverter implements Converter, ErrorConverter { final Serializers serializers; - final JsonConverter jsonConverter = JsonConverter(); + static const JsonConverter jsonConverter = JsonConverter(); final Type? errorType; /// Builds a new BuiltValueConverter instance that uses built_value serializers defined @@ -17,10 +17,10 @@ class BuiltValueConverter implements Converter, ErrorConverter { /// If the error body cannot be converted with serializers and [errorType] is provided /// and it's not `null`, BuiltValueConverter will try to deserialize the error body into /// [errorType]. - BuiltValueConverter(this.serializers, {this.errorType}); + const BuiltValueConverter(this.serializers, {this.errorType}); T? _deserialize(dynamic value) { - var serializer; + dynamic serializer; if (value is Map && value.containsKey('\$')) { serializer = serializers.serializerForWireName(value['\$']); } @@ -34,7 +34,9 @@ class BuiltValueConverter implements Converter, ErrorConverter { } BuiltList _deserializeListOf(Iterable value) { - final deserialized = value.map((value) => _deserialize(value)); + final Iterable deserialized = + value.map((value) => _deserialize(value)); + return BuiltList(deserialized.toList(growable: false)); } @@ -43,29 +45,33 @@ class BuiltValueConverter implements Converter, ErrorConverter { if (entity is Iterable) { return _deserializeListOf(entity) as BodyType; } + return _deserialize(entity); } @override - Request convertRequest(Request request) { - request = request.copyWith(body: serializers.serialize(request.body)); - return jsonConverter.convertRequest(request); - } + Request convertRequest(Request request) => jsonConverter.convertRequest( + request.copyWith(body: serializers.serialize(request.body)), + ); @override FutureOr> convertResponse( - Response response) async { - final jsonResponse = await jsonConverter.convertResponse(response); - final body = deserialize(jsonResponse.body); - return jsonResponse.copyWith(body: body); + Response response, + ) async { + final Response jsonResponse = await jsonConverter.convertResponse(response); + + return jsonResponse.copyWith( + body: deserialize(jsonResponse.body), + ); } @override FutureOr convertError( - Response response) async { - final jsonResponse = await jsonConverter.convertResponse(response); + Response response, + ) async { + final Response jsonResponse = await jsonConverter.convertResponse(response); - var body; + dynamic body; try { // try to deserialize using wireName diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 88ccd7fc..b96ad512 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -5,7 +5,7 @@ documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value- repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: built_value: ^8.0.0 @@ -18,7 +18,8 @@ dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 built_value_generator: ^8.0.6 - pedantic: ^1.10.0 + dart_code_metrics: ^4.8.1 + lints: ^2.0.0 dependency_overrides: # Comment before publish diff --git a/chopper_built_value/test/data.g.dart b/chopper_built_value/test/data.g.dart index df30695c..d9413768 100644 --- a/chopper_built_value/test/data.g.dart +++ b/chopper_built_value/test/data.g.dart @@ -35,17 +35,17 @@ class _$DataModelSerializer implements StructuredSerializer { final iterator = serialized.iterator; while (iterator.moveNext()) { - final key = iterator.current as String; + final key = iterator.current! as String; iterator.moveNext(); final Object? value = iterator.current; switch (key) { case 'id': result.id = serializers.deserialize(value, - specifiedType: const FullType(int)) as int; + specifiedType: const FullType(int))! as int; break; case 'name': result.name = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; } } @@ -79,13 +79,13 @@ class _$ErrorModelSerializer implements StructuredSerializer { final iterator = serialized.iterator; while (iterator.moveNext()) { - final key = iterator.current as String; + final key = iterator.current! as String; iterator.moveNext(); final Object? value = iterator.current; switch (key) { case 'message': result.message = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; } } @@ -101,11 +101,11 @@ class _$DataModel extends DataModel { final String name; factory _$DataModel([void Function(DataModelBuilder)? updates]) => - (new DataModelBuilder()..update(updates)).build(); + (new DataModelBuilder()..update(updates))._build(); _$DataModel._({required this.id, required this.name}) : super._() { - BuiltValueNullFieldError.checkNotNull(id, 'DataModel', 'id'); - BuiltValueNullFieldError.checkNotNull(name, 'DataModel', 'name'); + BuiltValueNullFieldError.checkNotNull(id, r'DataModel', 'id'); + BuiltValueNullFieldError.checkNotNull(name, r'DataModel', 'name'); } @override @@ -128,7 +128,7 @@ class _$DataModel extends DataModel { @override String toString() { - return (newBuiltValueToStringHelper('DataModel') + return (newBuiltValueToStringHelper(r'DataModel') ..add('id', id) ..add('name', name)) .toString(); @@ -170,12 +170,14 @@ class DataModelBuilder implements Builder { } @override - _$DataModel build() { + DataModel build() => _build(); + + _$DataModel _build() { final _$result = _$v ?? new _$DataModel._( - id: BuiltValueNullFieldError.checkNotNull(id, 'DataModel', 'id'), + id: BuiltValueNullFieldError.checkNotNull(id, r'DataModel', 'id'), name: BuiltValueNullFieldError.checkNotNull( - name, 'DataModel', 'name')); + name, r'DataModel', 'name')); replace(_$result); return _$result; } @@ -186,10 +188,10 @@ class _$ErrorModel extends ErrorModel { final String message; factory _$ErrorModel([void Function(ErrorModelBuilder)? updates]) => - (new ErrorModelBuilder()..update(updates)).build(); + (new ErrorModelBuilder()..update(updates))._build(); _$ErrorModel._({required this.message}) : super._() { - BuiltValueNullFieldError.checkNotNull(message, 'ErrorModel', 'message'); + BuiltValueNullFieldError.checkNotNull(message, r'ErrorModel', 'message'); } @override @@ -212,7 +214,7 @@ class _$ErrorModel extends ErrorModel { @override String toString() { - return (newBuiltValueToStringHelper('ErrorModel')..add('message', message)) + return (newBuiltValueToStringHelper(r'ErrorModel')..add('message', message)) .toString(); } } @@ -247,14 +249,16 @@ class ErrorModelBuilder implements Builder { } @override - _$ErrorModel build() { + ErrorModel build() => _build(); + + _$ErrorModel _build() { final _$result = _$v ?? new _$ErrorModel._( message: BuiltValueNullFieldError.checkNotNull( - message, 'ErrorModel', 'message')); + message, r'ErrorModel', 'message')); replace(_$result); return _$result; } } -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas diff --git a/chopper_built_value/test/serializers.dart b/chopper_built_value/test/serializers.dart index 370f4b37..7124a7c1 100644 --- a/chopper_built_value/test/serializers.dart +++ b/chopper_built_value/test/serializers.dart @@ -1,6 +1,7 @@ library serializers; import 'package:built_value/serializer.dart'; + import 'data.dart'; part 'serializers.g.dart'; diff --git a/chopper_built_value/test/serializers.g.dart b/chopper_built_value/test/serializers.g.dart index 6cb3a9e7..55d2a7d3 100644 --- a/chopper_built_value/test/serializers.g.dart +++ b/chopper_built_value/test/serializers.g.dart @@ -11,4 +11,4 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ErrorModel.serializer)) .build(); -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas diff --git a/chopper_generator/analysis_options.yaml b/chopper_generator/analysis_options.yaml index d4fcc1ad..57256269 100644 --- a/chopper_generator/analysis_options.yaml +++ b/chopper_generator/analysis_options.yaml @@ -1 +1,35 @@ -include: package:pedantic/analysis_options.yaml \ No newline at end of file +include: package:lints/recommended.yaml + +analyzer: + exclude: + - "**.g.dart" + - "**.chopper.dart" + - "**.mocks.dart" + - "example/**" + plugins: + - dart_code_metrics + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-arguments: 4 + maximum-nesting-level: 5 + number-of-parameters: 5 + source-lines-of-code: 200 + metrics-exclude: + - test/** + rules: + - newline-before-return + - no-boolean-literal-compare + - no-empty-block + - prefer-trailing-comma + - prefer-conditional-expressions + - no-equal-then-else + anti-patterns: + - long-method + - long-parameter-list + +linter: + rules: + avoid_print: true + prefer_single_quotes: true diff --git a/chopper_generator/lib/chopper_generator.dart b/chopper_generator/lib/chopper_generator.dart index 2facc8b0..74355389 100644 --- a/chopper_generator/lib/chopper_generator.dart +++ b/chopper_generator/lib/chopper_generator.dart @@ -1,6 +1,7 @@ library chopper_generator.dart; import 'package:build/build.dart'; + import 'src/generator.dart'; Builder chopperGeneratorFactory(BuilderOptions options) => diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index ab95ee80..13b3c606 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -1,32 +1,28 @@ ///@nodoc import 'dart:async'; +import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; - import 'package:build/build.dart'; import 'package:built_collection/built_collection.dart'; -import 'package:dart_style/dart_style.dart'; - -import 'package:source_gen/source_gen.dart'; - -// TODO(lejard_h) Code builder not null safe yet -// ignore: import_of_legacy_library_into_null_safe -import 'package:code_builder/code_builder.dart'; import 'package:chopper/chopper.dart' as chopper; +import 'package:code_builder/code_builder.dart'; +import 'package:dart_style/dart_style.dart'; import 'package:logging/logging.dart'; +import 'package:source_gen/source_gen.dart'; -const _clientVar = 'client'; -const _baseUrlVar = 'baseUrl'; -const _parametersVar = '\$params'; -const _headersVar = '\$headers'; -const _requestVar = '\$request'; -const _bodyVar = '\$body'; -const _partsVar = '\$parts'; -const _urlVar = '\$url'; +const String _clientVar = 'client'; +const String _baseUrlVar = 'baseUrl'; +const String _parametersVar = r'$params'; +const String _headersVar = r'$headers'; +const String _requestVar = r'$request'; +const String _bodyVar = r'$body'; +const String _partsVar = r'$parts'; +const String _urlVar = r'$url'; -final _logger = Logger('Chopper Generator'); +final Logger _logger = Logger('Chopper Generator'); class ChopperGenerator extends GeneratorForAnnotation { @override @@ -36,7 +32,7 @@ class ChopperGenerator extends GeneratorForAnnotation { BuildStep buildStep, ) { if (element is! ClassElement) { - final friendlyName = element.displayName; + final String friendlyName = element.displayName; throw InvalidGenerationSourceError( 'Generator cannot target `$friendlyName`.', todo: 'Remove the [ChopperApi] annotation from `$friendlyName`.', @@ -62,18 +58,18 @@ class ChopperGenerator extends GeneratorForAnnotation { ClassElement element, ) { if (!element.allSupertypes.any(_extendsChopperService)) { - final friendlyName = element.displayName; + final String friendlyName = element.displayName; throw InvalidGenerationSourceError( 'Generator cannot target `$friendlyName`.', todo: '`$friendlyName` need to extends the [ChopperService] class.', ); } - final friendlyName = element.name; - final name = '_\$$friendlyName'; - final baseUrl = annotation.peek(_baseUrlVar)?.stringValue ?? ''; + final String friendlyName = element.name; + final String name = '_\$$friendlyName'; + final String baseUrl = annotation.peek(_baseUrlVar)?.stringValue ?? ''; - final classBuilder = Class((builder) { + final Class classBuilder = Class((builder) { builder ..name = name ..extend = refer(friendlyName) @@ -82,57 +78,70 @@ class ChopperGenerator extends GeneratorForAnnotation { ..methods.addAll(_parseMethods(element, baseUrl)); }); - final ignore = + final String ignore = '// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps'; - final emitter = DartEmitter(); + final DartEmitter emitter = DartEmitter(); + return DartFormatter().format('$ignore\n${classBuilder.accept(emitter)}'); } - Constructor _generateConstructor() => Constructor((constructorBuilder) { - constructorBuilder.optionalParameters.add( - Parameter((paramBuilder) { - paramBuilder.name = _clientVar; - paramBuilder.type = refer('${chopper.ChopperClient}?'); - }), - ); + Constructor _generateConstructor() => Constructor( + (ConstructorBuilder constructorBuilder) { + constructorBuilder.optionalParameters.add( + Parameter((paramBuilder) { + paramBuilder.name = _clientVar; + paramBuilder.type = refer('${chopper.ChopperClient}?'); + }), + ); - constructorBuilder.body = Code( - 'if ($_clientVar == null) return;\nthis.$_clientVar = $_clientVar;', - ); - }); - - Iterable _parseMethods(ClassElement element, String baseUrl) { - return element.methods.where((MethodElement method) { - final methodAnnotation = _getMethodAnnotation(method); - return methodAnnotation != null && - method.isAbstract && - method.returnType.isDartAsyncFuture; - }).map((MethodElement m) => _generateMethod(m, baseUrl)); - } + constructorBuilder.body = Code( + 'if ($_clientVar == null) return;\nthis.$_clientVar = $_clientVar;', + ); + }, + ); + + Iterable _parseMethods(ClassElement element, String baseUrl) => + element.methods + .where( + (MethodElement method) => + _getMethodAnnotation(method) != null && + method.isAbstract && + method.returnType.isDartAsyncFuture, + ) + .map((MethodElement m) => _generateMethod(m, baseUrl)); Method _generateMethod(MethodElement m, String baseUrl) { - final method = _getMethodAnnotation(m); - final multipart = _hasAnnotation(m, chopper.Multipart); - final factoryConverter = _getFactoryConverterAnnotation(m); - - final body = _getAnnotation(m, chopper.Body); - final paths = _getAnnotations(m, chopper.Path); - final queries = _getAnnotations(m, chopper.Query); - final queryMap = _getAnnotation(m, chopper.QueryMap); - final fields = _getAnnotations(m, chopper.Field); - final fieldMap = _getAnnotation(m, chopper.FieldMap); - final parts = _getAnnotations(m, chopper.Part); - final partMap = _getAnnotation(m, chopper.PartMap); - final fileFields = _getAnnotations(m, chopper.PartFile); - final fileFieldMap = _getAnnotation(m, chopper.PartFileMap); - - final headers = _generateHeaders(m, method!); - final url = _generateUrl(method, paths, baseUrl); - final responseType = _getResponseType(m.returnType); - final responseInnerType = + final ConstantReader? method = _getMethodAnnotation(m); + final bool multipart = _hasAnnotation(m, chopper.Multipart); + final ConstantReader? factoryConverter = _getFactoryConverterAnnotation(m); + + final Map body = _getAnnotation(m, chopper.Body); + final Map paths = + _getAnnotations(m, chopper.Path); + final Map queries = + _getAnnotations(m, chopper.Query); + final Map queryMap = + _getAnnotation(m, chopper.QueryMap); + final Map fields = + _getAnnotations(m, chopper.Field); + final Map fieldMap = + _getAnnotation(m, chopper.FieldMap); + final Map parts = + _getAnnotations(m, chopper.Part); + final Map partMap = + _getAnnotation(m, chopper.PartMap); + final Map fileFields = + _getAnnotations(m, chopper.PartFile); + final Map fileFieldMap = + _getAnnotation(m, chopper.PartFileMap); + + final Code? headers = _generateHeaders(m, method!); + final Expression url = _generateUrl(method, paths, baseUrl); + final DartType? responseType = _getResponseType(m.returnType); + final DartType? responseInnerType = _getResponseInnerType(m.returnType) ?? responseType; - return Method((b) { + return Method((MethodBuilder b) { b.annotations.add(refer('override')); b.name = m.displayName; @@ -164,7 +173,7 @@ class ChopperGenerator extends GeneratorForAnnotation { m.parameters.where((p) => p.isNamed).map(buildNamedParam), ); - final blocks = [ + final List blocks = [ url.assignFinal(_urlVar).statement, ]; @@ -173,12 +182,12 @@ class ChopperGenerator extends GeneratorForAnnotation { } // Build an iterable of all the parameters that are nullable - final optionalNullableParameters = [ + final Iterable optionalNullableParameters = [ ...m.parameters.where((p) => p.isOptionalPositional), ...m.parameters.where((p) => p.isNamed), ].where((el) => el.type.isNullable).map((el) => el.name); - final hasQueryMap = queryMap.isNotEmpty; + final bool hasQueryMap = queryMap.isNotEmpty; if (hasQueryMap) { if (queries.isNotEmpty) { blocks.add(refer('$_parametersVar.addAll').call( @@ -204,16 +213,16 @@ class ChopperGenerator extends GeneratorForAnnotation { } } - final hasQuery = hasQueryMap || queries.isNotEmpty; + final bool hasQuery = hasQueryMap || queries.isNotEmpty; if (headers != null) { blocks.add(headers); } - final methodOptionalBody = getMethodOptionalBody(method); - final methodName = getMethodName(method); - final methodUrl = getMethodPath(method); - var hasBody = body.isNotEmpty || fields.isNotEmpty; + final bool methodOptionalBody = getMethodOptionalBody(method); + final String methodName = getMethodName(method); + final String methodUrl = getMethodPath(method); + bool hasBody = body.isNotEmpty || fields.isNotEmpty; if (hasBody) { if (body.isNotEmpty) { blocks.add( @@ -226,7 +235,7 @@ class ChopperGenerator extends GeneratorForAnnotation { } } - final hasFieldMap = fieldMap.isNotEmpty; + final bool hasFieldMap = fieldMap.isNotEmpty; if (hasFieldMap) { if (hasBody) { blocks.add(refer('$_bodyVar.addAll').call( @@ -241,14 +250,14 @@ class ChopperGenerator extends GeneratorForAnnotation { hasBody = hasBody || hasFieldMap; - var hasParts = - multipart == true && (parts.isNotEmpty || fileFields.isNotEmpty); + bool hasParts = multipart && (parts.isNotEmpty || fileFields.isNotEmpty); if (hasParts) { blocks.add( - _generateList(parts, fileFields).assignFinal(_partsVar).statement); + _generateList(parts, fileFields).assignFinal(_partsVar).statement, + ); } - final hasPartMap = multipart == true && partMap.isNotEmpty; + final bool hasPartMap = multipart && partMap.isNotEmpty; if (hasPartMap) { if (hasParts) { blocks.add(refer('$_partsVar.addAll').call( @@ -261,7 +270,7 @@ class ChopperGenerator extends GeneratorForAnnotation { } } - final hasFileFilesMap = multipart == true && fileFieldMap.isNotEmpty; + final bool hasFileFilesMap = multipart && fileFieldMap.isNotEmpty; if (hasFileFilesMap) { if (hasParts || hasPartMap) { blocks.add(refer('$_partsVar.addAll').call( @@ -295,26 +304,30 @@ class ChopperGenerator extends GeneratorForAnnotation { hasParts: hasParts, ).assignFinal(_requestVar).statement); - final namedArguments = {}; + final Map namedArguments = {}; - final requestFactory = factoryConverter?.peek('request'); + final ConstantReader? requestFactory = factoryConverter?.peek('request'); if (requestFactory != null) { - final func = requestFactory.objectValue.toFunctionValue(); + final ExecutableElement? func = + requestFactory.objectValue.toFunctionValue(); namedArguments['requestConverter'] = refer(_factoryForFunction(func!)); } - final responseFactory = factoryConverter?.peek('response'); + final ConstantReader? responseFactory = + factoryConverter?.peek('response'); if (responseFactory != null) { - final func = responseFactory.objectValue.toFunctionValue(); + final ExecutableElement? func = + responseFactory.objectValue.toFunctionValue(); namedArguments['responseConverter'] = refer(_factoryForFunction(func!)); } - final typeArguments = []; + final List typeArguments = []; if (responseType != null) { typeArguments .add(refer(responseType.getDisplayString(withNullability: false))); typeArguments.add( - refer(responseInnerType!.getDisplayString(withNullability: false))); + refer(responseInnerType!.getDisplayString(withNullability: false)), + ); } blocks.add(refer('$_clientVar.send') @@ -326,39 +339,42 @@ class ChopperGenerator extends GeneratorForAnnotation { }); } - String _factoryForFunction(FunctionTypedElement function) { - if (function.enclosingElement is ClassElement) { - return '${function.enclosingElement!.name}.${function.name}'; - } - return function.name!; - } + String _factoryForFunction(FunctionTypedElement function) => + function.enclosingElement is ClassElement + ? '${function.enclosingElement!.name}.${function.name}' + : function.name!; Map _getAnnotation(MethodElement method, Type type) { - var annotation; - var name = ''; - for (final p in method.parameters) { - dynamic a = _typeChecker(type).firstAnnotationOf(p); + DartObject? annotation; + String name = ''; + + for (final ParameterElement p in method.parameters) { + DartObject? a = _typeChecker(type).firstAnnotationOf(p); if (annotation != null && a != null) { throw Exception( - 'Too many $type annotation for \'${method.displayName}\''); + 'Too many $type annotation for \'${method.displayName}\'', + ); } else if (annotation == null && a != null) { annotation = a; name = p.displayName; } } - if (annotation == null) return {}; - return {name: ConstantReader(annotation)}; + + return annotation == null ? {} : {name: ConstantReader(annotation)}; } Map _getAnnotations( - MethodElement m, Type type) { - var annotation = {}; - for (final p in m.parameters) { - final a = _typeChecker(type).firstAnnotationOf(p); + MethodElement m, + Type type, + ) { + Map annotation = {}; + for (final ParameterElement p in m.parameters) { + final DartObject? a = _typeChecker(type).firstAnnotationOf(p); if (a != null) { annotation[p] = ConstantReader(a); } } + return annotation; } @@ -366,28 +382,28 @@ class ChopperGenerator extends GeneratorForAnnotation { ConstantReader? _getMethodAnnotation(MethodElement method) { for (final type in _methodsAnnotations) { - final annotation = _typeChecker(type) + final DartObject? annotation = _typeChecker(type) .firstAnnotationOf(method, throwOnUnresolved: false); - if (annotation != null) return ConstantReader(annotation); + if (annotation != null) { + return ConstantReader(annotation); + } } + return null; } ConstantReader? _getFactoryConverterAnnotation(MethodElement method) { - final annotation = _typeChecker(chopper.FactoryConverter) + final DartObject? annotation = _typeChecker(chopper.FactoryConverter) .firstAnnotationOf(method, throwOnUnresolved: false); - if (annotation != null) return ConstantReader(annotation); - return null; - } - bool _hasAnnotation(MethodElement method, Type type) { - final annotation = - _typeChecker(type).firstAnnotationOf(method, throwOnUnresolved: false); - - return annotation != null; + return annotation != null ? ConstantReader(annotation) : null; } - final _methodsAnnotations = const [ + bool _hasAnnotation(MethodElement method, Type type) => + _typeChecker(type).firstAnnotationOf(method, throwOnUnresolved: false) != + null; + + final List _methodsAnnotations = const [ chopper.Get, chopper.Post, chopper.Delete, @@ -398,18 +414,15 @@ class ChopperGenerator extends GeneratorForAnnotation { chopper.Options, ]; - DartType? _genericOf(DartType? type) { - return type is InterfaceType && type.typeArguments.isNotEmpty - ? type.typeArguments.first - : null; - } + DartType? _genericOf(DartType? type) => + type is InterfaceType && type.typeArguments.isNotEmpty + ? type.typeArguments.first + : null; - DartType? _getResponseType(DartType type) { - return _genericOf(_genericOf(type)); - } + DartType? _getResponseType(DartType type) => _genericOf(_genericOf(type)); DartType? _getResponseInnerType(DartType type) { - final generic = _genericOf(type); + final DartType? generic = _genericOf(type); if (generic == null || _typeChecker(Map).isExactlyType(type) || @@ -428,9 +441,9 @@ class ChopperGenerator extends GeneratorForAnnotation { Map paths, String baseUrl, ) { - var path = getMethodPath(method); + String path = getMethodPath(method); paths.forEach((p, ConstantReader r) { - final name = r.peek('name')?.stringValue ?? p.displayName; + final String name = r.peek('name')?.stringValue ?? p.displayName; path = path.replaceFirst('{$name}', '\${${p.displayName}}'); }); @@ -459,13 +472,13 @@ class ChopperGenerator extends GeneratorForAnnotation { bool useQueries = false, bool useHeaders = false, }) { - final params = [ + final List params = [ literal(getMethodName(method)), refer(_urlVar), refer('$_clientVar.$_baseUrlVar'), ]; - final namedParams = {}; + final Map namedParams = {}; if (hasBody) { namedParams['body'] = refer(_bodyVar); @@ -488,9 +501,9 @@ class ChopperGenerator extends GeneratorForAnnotation { } Expression _generateMap(Map queries) { - final map = {}; - queries.forEach((p, ConstantReader r) { - final name = r.peek('name')?.stringValue ?? p.displayName; + final Map map = {}; + queries.forEach((ParameterElement p, ConstantReader r) { + final String name = r.peek('name')?.stringValue ?? p.displayName; map[literal(name)] = refer(p.displayName); }); @@ -501,21 +514,21 @@ class ChopperGenerator extends GeneratorForAnnotation { Map parts, Map fileFields, ) { - final list = []; + final List list = []; parts.forEach((p, ConstantReader r) { - final name = r.peek('name')?.stringValue ?? p.displayName; - final params = [ + final String name = r.peek('name')?.stringValue ?? p.displayName; + final List params = [ literal(name), refer(p.displayName), ]; list.add(refer( - 'PartValue<${p.type.getDisplayString(withNullability: p.type.isNullable)}>') - .newInstance(params)); + 'PartValue<${p.type.getDisplayString(withNullability: p.type.isNullable)}>', + ).newInstance(params)); }); fileFields.forEach((p, ConstantReader r) { - final name = r.peek('name')?.stringValue ?? p.displayName; - final params = [ + final String name = r.peek('name')?.stringValue ?? p.displayName; + final List params = [ literal(name), refer(p.displayName), ]; @@ -525,18 +538,20 @@ class ChopperGenerator extends GeneratorForAnnotation { .newInstance(params), ); }); + return literalList(list, refer('PartValue')); } Code? _generateHeaders(MethodElement methodElement, ConstantReader method) { - final codeBuffer = StringBuffer('')..writeln('{'); + final StringBuffer codeBuffer = StringBuffer('')..writeln('{'); // Search for @Header anotation in method parameters - final annotations = _getAnnotations(methodElement, chopper.Header); + final Map annotations = + _getAnnotations(methodElement, chopper.Header); annotations.forEach((parameter, ConstantReader annotation) { - final paramName = parameter.displayName; - final name = annotation.peek('name')?.stringValue ?? paramName; + final String paramName = parameter.displayName; + final String name = annotation.peek('name')?.stringValue ?? paramName; if (parameter.type.isNullable) { codeBuffer.writeln('if ($paramName != null) \'$name\': $paramName,'); @@ -545,10 +560,11 @@ class ChopperGenerator extends GeneratorForAnnotation { } }); - final headersReader = method.peek('headers'); + final ConstantReader? headersReader = method.peek('headers'); if (headersReader == null) return null; - final methodAnnotations = headersReader.mapValue; + final Map methodAnnotations = + headersReader.mapValue; methodAnnotations.forEach((headerName, headerValue) { if (headerName != null && headerValue != null) { @@ -559,12 +575,9 @@ class ChopperGenerator extends GeneratorForAnnotation { }); codeBuffer.writeln('};'); - final code = codeBuffer.toString(); - if (code == '{\n};\n') { - return null; - } + final String code = codeBuffer.toString(); - return Code('final $_headersVar = $code'); + return code == '{\n};\n' ? null : Code('final $_headersVar = $code'); } } @@ -587,45 +600,41 @@ extension DartTypeExtension on DartType { } // 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), - ), - ); -} +Parameter buildRequiredPositionalParam(ParameterElement p) => Parameter( + (ParameterBuilder 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), - ); +Parameter buildOptionalPositionalParam(ParameterElement p) => + Parameter((ParameterBuilder pb) { + pb + ..name = p.name + ..type = Reference( + p.type.getDisplayString(withNullability: p.type.isNullable), + ); - if (p.defaultValueCode != null) { - pb.defaultTo = Code(p.defaultValueCode!); - } - }); -} + 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), - ); +Parameter buildNamedParam(ParameterElement p) => + Parameter((ParameterBuilder 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!); - } - }); -} + if (p.defaultValueCode != null) { + pb.defaultTo = Code(p.defaultValueCode!); + } + }); diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index ef2adb98..b868926a 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -5,22 +5,23 @@ documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: analyzer: ">=4.1.0 <4.3.0" build: ^2.0.0 built_collection: ^5.0.0 chopper: ^4.0.0 - code_builder: ^4.0.0 + code_builder: ^4.1.0 dart_style: ^2.0.0 logging: ^1.0.0 meta: ^1.3.0 source_gen: ^1.0.0 dev_dependencies: - pedantic: ^1.11.0 - test: ^1.15.4 + test: ^1.16.4 + dart_code_metrics: ^4.8.1 + lints: ^2.0.0 dependency_overrides: # Comment before publish diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 00000000..7061686f --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,32 @@ +include: package:lints/recommended.yaml + +analyzer: + exclude: + - "**.g.dart" + - "**.chopper.dart" + - "**.mocks.dart" + plugins: + - dart_code_metrics + +dart_code_metrics: + metrics: + cyclomatic-complexity: 20 + number-of-arguments: 4 + maximum-nesting-level: 5 + metrics-exclude: + - test/** + rules: + - newline-before-return + - no-boolean-literal-compare + - no-empty-block + - prefer-trailing-comma + - prefer-conditional-expressions + - no-equal-then-else + anti-patterns: + - long-method + - long-parameter-list + +linter: + rules: + avoid_print: false + prefer_single_quotes: true diff --git a/example/bin/main_built_value.dart b/example/bin/main_built_value.dart index a92630ea..9f87becc 100644 --- a/example/bin/main_built_value.dart +++ b/example/bin/main_built_value.dart @@ -2,29 +2,32 @@ import 'dart:async'; import 'package:built_collection/built_collection.dart'; import 'package:built_value/serializer.dart'; +import 'package:built_value/standard_json_plugin.dart'; import 'package:chopper/chopper.dart'; import 'package:chopper_example/built_value_resource.dart'; import 'package:chopper_example/built_value_serializers.dart'; -import 'package:built_value/standard_json_plugin.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; final jsonSerializers = - (serializers.toBuilder()..addPlugin(new StandardJsonPlugin())).build(); + (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); /// Simple client to have working example without remote server final client = MockClient((req) async { - if (req.method == 'POST') + if (req.method == 'POST') { return http.Response('{"type":"Fatal","message":"fatal erorr"}', 500); - if (req.url.path == '/resources/list') + } + if (req.url.path == '/resources/list') { return http.Response('[{"id":"1","name":"Foo"}]', 200); + } + return http.Response('{"id":"1","name":"Foo"}', 200); }); main() async { - final chopper = new ChopperClient( + final chopper = ChopperClient( client: client, - baseUrl: "http://localhost:8000", + baseUrl: 'http://localhost:8000', converter: BuiltValueConverter(), errorConverter: BuiltValueConverter(), services: [ @@ -35,7 +38,7 @@ main() async { final myService = chopper.getService(); - final response1 = await myService.getResource("1"); + final response1 = await myService.getResource('1'); print('response 1: ${response1.body}'); // undecoded String final response2 = await myService.getTypedResource(); @@ -46,8 +49,8 @@ main() async { try { final builder = ResourceBuilder() - ..id = "3" - ..name = "Super Name"; + ..id = '3' + ..name = 'Super Name'; await myService.newResource(builder.build()); } on Response catch (error) { print(error.body); @@ -58,12 +61,10 @@ class BuiltValueConverter extends JsonConverter { T? _deserialize(dynamic value) { final serializer = jsonSerializers.serializerForType(T) as Serializer?; if (serializer == null) { - throw Exception('No serializer for type ${T}'); + throw Exception('No serializer for type $T'); } - return jsonSerializers.deserializeWith( - serializer, - value, - ); + + return jsonSerializers.deserializeWith(serializer, value); } BuiltList _deserializeListOf(Iterable value) => BuiltList( @@ -77,20 +78,24 @@ class BuiltValueConverter extends JsonConverter { if (entity is T) return entity; try { - if (entity is List) return _deserializeListOf(entity); - return _deserialize(entity); + return entity is List + ? _deserializeListOf(entity) + : _deserialize(entity); } catch (e) { print(e); + return null; } } @override FutureOr> convertResponse( - Response response) async { + Response response, + ) async { // use [JsonConverter] to decode json - final jsonRes = await super.convertResponse(response); + final Response jsonRes = await super.convertResponse(response); final body = _decode(jsonRes.body); + return jsonRes.copyWith(body: body); } diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index 75851acd..956014f5 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -1,16 +1,19 @@ -import "dart:async"; +import 'dart:async'; + import 'package:chopper/chopper.dart'; import 'package:chopper_example/json_serializable.dart'; - import 'package:http/http.dart' as http; import 'package:http/testing.dart'; /// Simple client to have working example without remote server final client = MockClient((req) async { - if (req.method == 'POST') + if (req.method == 'POST') { return http.Response('{"type":"Fatal","message":"fatal erorr"}', 500); - if (req.method == 'GET' && req.headers['test'] == 'list') + } + if (req.method == 'GET' && req.headers['test'] == 'list') { return http.Response('[{"id":"1","name":"Foo"}]', 200); + } + return http.Response('{"id":"1","name":"Foo"}', 200); }); @@ -21,7 +24,7 @@ main() async { final chopper = ChopperClient( client: client, - baseUrl: "http://localhost:8000", + baseUrl: 'http://localhost:8000', // bind your object factories here converter: converter, errorConverter: converter, @@ -35,7 +38,7 @@ main() async { final myService = chopper.getService(); - final response1 = await myService.getResource("1"); + final response1 = await myService.getResource('1'); print('response 1: ${response1.body}'); // undecoded String final response2 = await myService.getResources(); @@ -44,11 +47,11 @@ main() async { final response3 = await myService.getTypedResource(); print('response 3: ${response3.body}'); // decoded Resource - final response4 = await myService.getMapResource("1"); + final response4 = await myService.getMapResource('1'); print('response 4: ${response4.body}'); // undecoded Resource try { - await myService.newResource(Resource("3", "Super Name")); + await myService.newResource(Resource('3', 'Super Name')); } on Response catch (error) { print(error.body); } @@ -56,8 +59,8 @@ main() async { Future authHeader(Request request) async => applyHeader( request, - "Authorization", - "42", + 'Authorization', + '42', ); typedef JsonFactory = T Function(Map json); @@ -92,7 +95,8 @@ class JsonSerializableConverter extends JsonConverter { @override FutureOr> convertResponse( - Response response) async { + Response response, + ) async { // use [JsonConverter] to decode json final jsonRes = await super.convertResponse(response); @@ -101,8 +105,10 @@ class JsonSerializableConverter extends JsonConverter { @override // all objects should implements toJson method + // ignore: unnecessary_overrides Request convertRequest(Request request) => super.convertRequest(request); + @override FutureOr convertError(Response response) async { // use [JsonConverter] to decode json final jsonRes = await super.convertError(response); diff --git a/example/lib/built_value_resource.dart b/example/lib/built_value_resource.dart index 6ea4e35b..9e30c3ed 100644 --- a/example/lib/built_value_resource.dart +++ b/example/lib/built_value_resource.dart @@ -7,44 +7,51 @@ import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; import 'package:chopper/chopper.dart'; -part 'built_value_resource.g.dart'; part 'built_value_resource.chopper.dart'; +part 'built_value_resource.g.dart'; abstract class Resource implements Built { String get id; + String get name; static Serializer get serializer => _$resourceSerializer; - factory Resource([updates(ResourceBuilder b)]) = _$Resource; + factory Resource([Function(ResourceBuilder b) updates]) = _$Resource; + Resource._(); } abstract class ResourceError implements Built { String get type; + String get message; static Serializer get serializer => _$resourceErrorSerializer; - factory ResourceError([updates(ResourceErrorBuilder b)]) = _$ResourceError; + factory ResourceError([Function(ResourceErrorBuilder b) updates]) = + _$ResourceError; + ResourceError._(); } -@ChopperApi(baseUrl: "/resources") +@ChopperApi(baseUrl: '/resources') abstract class MyService extends ChopperService { static MyService create([ChopperClient? client]) => _$MyService(client); - @Get(path: "/{id}/") + @Get(path: '/{id}/') Future getResource(@Path() String id); - @Get(path: "/list") + @Get(path: '/list') Future>> getBuiltListResources(); - @Get(path: "/", headers: const {"foo": "bar"}) + @Get(path: '/', headers: {'foo': 'bar'}) Future> getTypedResource(); @Post() - Future> newResource(@Body() Resource resource, - {@Header() String? name}); + Future> newResource( + @Body() Resource resource, { + @Header() String? name, + }); } diff --git a/example/lib/built_value_resource.g.dart b/example/lib/built_value_resource.g.dart index 8030092d..bc969e05 100644 --- a/example/lib/built_value_resource.g.dart +++ b/example/lib/built_value_resource.g.dart @@ -36,17 +36,17 @@ class _$ResourceSerializer implements StructuredSerializer { final iterator = serialized.iterator; while (iterator.moveNext()) { - final key = iterator.current as String; + final key = iterator.current! as String; iterator.moveNext(); final Object? value = iterator.current; switch (key) { case 'id': result.id = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; case 'name': result.name = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; } } @@ -83,17 +83,17 @@ class _$ResourceErrorSerializer implements StructuredSerializer { final iterator = serialized.iterator; while (iterator.moveNext()) { - final key = iterator.current as String; + final key = iterator.current! as String; iterator.moveNext(); final Object? value = iterator.current; switch (key) { case 'type': result.type = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; case 'message': result.message = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(String))! as String; break; } } @@ -109,11 +109,11 @@ class _$Resource extends Resource { final String name; factory _$Resource([void Function(ResourceBuilder)? updates]) => - (new ResourceBuilder()..update(updates)).build(); + (new ResourceBuilder()..update(updates))._build(); _$Resource._({required this.id, required this.name}) : super._() { - BuiltValueNullFieldError.checkNotNull(id, 'Resource', 'id'); - BuiltValueNullFieldError.checkNotNull(name, 'Resource', 'name'); + BuiltValueNullFieldError.checkNotNull(id, r'Resource', 'id'); + BuiltValueNullFieldError.checkNotNull(name, r'Resource', 'name'); } @override @@ -136,7 +136,7 @@ class _$Resource extends Resource { @override String toString() { - return (newBuiltValueToStringHelper('Resource') + return (newBuiltValueToStringHelper(r'Resource') ..add('id', id) ..add('name', name)) .toString(); @@ -178,12 +178,14 @@ class ResourceBuilder implements Builder { } @override - _$Resource build() { + Resource build() => _build(); + + _$Resource _build() { final _$result = _$v ?? new _$Resource._( - id: BuiltValueNullFieldError.checkNotNull(id, 'Resource', 'id'), + id: BuiltValueNullFieldError.checkNotNull(id, r'Resource', 'id'), name: BuiltValueNullFieldError.checkNotNull( - name, 'Resource', 'name')); + name, r'Resource', 'name')); replace(_$result); return _$result; } @@ -196,11 +198,11 @@ class _$ResourceError extends ResourceError { final String message; factory _$ResourceError([void Function(ResourceErrorBuilder)? updates]) => - (new ResourceErrorBuilder()..update(updates)).build(); + (new ResourceErrorBuilder()..update(updates))._build(); _$ResourceError._({required this.type, required this.message}) : super._() { - BuiltValueNullFieldError.checkNotNull(type, 'ResourceError', 'type'); - BuiltValueNullFieldError.checkNotNull(message, 'ResourceError', 'message'); + BuiltValueNullFieldError.checkNotNull(type, r'ResourceError', 'type'); + BuiltValueNullFieldError.checkNotNull(message, r'ResourceError', 'message'); } @override @@ -225,7 +227,7 @@ class _$ResourceError extends ResourceError { @override String toString() { - return (newBuiltValueToStringHelper('ResourceError') + return (newBuiltValueToStringHelper(r'ResourceError') ..add('type', type) ..add('message', message)) .toString(); @@ -268,16 +270,18 @@ class ResourceErrorBuilder } @override - _$ResourceError build() { + ResourceError build() => _build(); + + _$ResourceError _build() { final _$result = _$v ?? new _$ResourceError._( type: BuiltValueNullFieldError.checkNotNull( - type, 'ResourceError', 'type'), + type, r'ResourceError', 'type'), message: BuiltValueNullFieldError.checkNotNull( - message, 'ResourceError', 'message')); + message, r'ResourceError', 'message')); replace(_$result); return _$result; } } -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas diff --git a/example/lib/built_value_serializers.dart b/example/lib/built_value_serializers.dart index cc8ef450..256983bf 100644 --- a/example/lib/built_value_serializers.dart +++ b/example/lib/built_value_serializers.dart @@ -1,12 +1,13 @@ library serializers; import 'package:built_value/serializer.dart'; + import 'built_value_resource.dart'; part 'built_value_serializers.g.dart'; /// Collection of generated serializers for the built_value chat example. -@SerializersFor(const [ +@SerializersFor([ Resource, ResourceError, ]) diff --git a/example/lib/built_value_serializers.g.dart b/example/lib/built_value_serializers.g.dart index dbad2232..699b3f08 100644 --- a/example/lib/built_value_serializers.g.dart +++ b/example/lib/built_value_serializers.g.dart @@ -11,4 +11,4 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ResourceError.serializer)) .build(); -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas diff --git a/example/lib/json_serializable.dart b/example/lib/json_serializable.dart index 361f166a..4676bcab 100644 --- a/example/lib/json_serializable.dart +++ b/example/lib/json_serializable.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:chopper/chopper.dart'; import 'package:json_annotation/json_annotation.dart'; -part 'json_serializable.g.dart'; part 'json_serializable.chopper.dart'; +part 'json_serializable.g.dart'; @JsonSerializable() class Resource { @@ -33,23 +33,25 @@ class ResourceError { Map toJson() => _$ResourceErrorToJson(this); } -@ChopperApi(baseUrl: "/resources") +@ChopperApi(baseUrl: '/resources') abstract class MyService extends ChopperService { static MyService create([ChopperClient? client]) => _$MyService(client); - @Get(path: "/{id}/") + @Get(path: '/{id}/') Future getResource(@Path() String id); - @Get(path: "/all", headers: const {"test": "list"}) + @Get(path: '/all', headers: {'test': 'list'}) Future>> getResources(); - @Get(path: "/") + @Get(path: '/') Future> getMapResource(@Query() String id); - @Get(path: "/", headers: const {"foo": "bar"}) + @Get(path: '/', headers: {'foo': 'bar'}) Future> getTypedResource(); @Post() - Future> newResource(@Body() Resource resource, - {@Header() String? name}); + Future> newResource( + @Body() Resource resource, { + @Header() String? name, + }); } diff --git a/example/pubspec.lock b/example/pubspec.lock index fff2f649..572b2c64 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,14 +7,28 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "34.0.0" + version: "40.0.0" analyzer: dependency: "direct main" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "3.2.0" + version: "4.1.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" args: dependency: transitive description: @@ -35,7 +49,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.3.0" build_config: dependency: transitive description: @@ -49,21 +63,21 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.9" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.7" + version: "2.1.11" build_runner_core: dependency: transitive description: @@ -72,7 +86,7 @@ packages: source: hosted version: "7.2.3" built_collection: - dependency: transitive + dependency: "direct main" description: name: built_collection url: "https://pub.dartlang.org" @@ -91,7 +105,7 @@ packages: name: built_value_generator url: "https://pub.dartlang.org" source: hosted - version: "8.1.4" + version: "8.4.0" charcode: dependency: transitive description: @@ -119,14 +133,7 @@ packages: path: "../chopper_generator" relative: true source: path - version: "4.0.2" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.5" + version: "4.0.3" code_builder: dependency: transitive description: @@ -155,13 +162,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.2" + dart_code_metrics: + dependency: "direct dev" + description: + name: dart_code_metrics + url: "https://pub.dartlang.org" + source: hosted + version: "4.16.0" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.3" file: dependency: transitive description: @@ -197,8 +218,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - http: + html: dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" + http: + dependency: "direct main" description: name: http url: "https://pub.dartlang.org" @@ -245,7 +273,14 @@ packages: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "6.1.4" + version: "6.1.6" + lints: + dependency: "direct dev" + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" logging: dependency: transitive description: @@ -288,6 +323,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" pool: dependency: transitive description: @@ -336,14 +378,14 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.2.2" source_helper: dependency: transitive description: name: source_helper url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "1.3.2" source_span: dependency: transitive description: @@ -414,6 +456,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" yaml: dependency: transitive description: @@ -422,4 +471,4 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.16.0-100.0.dev <3.0.0" + dart: ">=2.17.0 <3.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9a88feed..9975770b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,19 +5,23 @@ documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.17.0 <3.0.0' dependencies: chopper: json_annotation: built_value: analyzer: + http: + built_collection: dev_dependencies: build_runner: chopper_generator: json_serializable: built_value_generator: + dart_code_metrics: ^4.8.1 + lints: ^2.0.0 dependency_overrides: chopper: From 02cf2e003317e593176f40bed84cea8b772e4942 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Tue, 13 Sep 2022 14:53:55 +0300 Subject: [PATCH 24/61] Version bumped for release (#352) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- chopper_built_value/CHANGELOG.md | 4 ++++ chopper_built_value/pubspec.yaml | 4 ++-- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 4 ++-- example/pubspec.lock | 2 +- 7 files changed, 18 insertions(+), 6 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index 6af68839..e78e1909 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.0.0 + +- API breaking changes (FutureOr) + ## 4.0.1 - Fix for the null safety support diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index cff2ec03..96817bb9 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.1 +version: 5.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/chopper_built_value/CHANGELOG.md b/chopper_built_value/CHANGELOG.md index df5a3b34..e3a476bc 100644 --- a/chopper_built_value/CHANGELOG.md +++ b/chopper_built_value/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.0 + +- Chopper upgraded + ## 1.0.0 - Null safety support diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index b96ad512..0bd63c23 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_built_value description: A built_value based Converter for Chopper. -version: 1.0.0 +version: 1.1.0 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper @@ -10,7 +10,7 @@ environment: dependencies: built_value: ^8.0.0 built_collection: ^5.0.0 - chopper: ^4.0.0 + chopper: ^5.0.0 http: ^0.13.0 dev_dependencies: diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index cec3211d..22074e42 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.0.0 + +- API breaking changes (FutureOr usage) + ## 4.0.3 - Analyzer dependency upgrade diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index b868926a..a5804be1 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 4.0.3 +version: 5.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper @@ -11,7 +11,7 @@ dependencies: analyzer: ">=4.1.0 <4.3.0" build: ^2.0.0 built_collection: ^5.0.0 - chopper: ^4.0.0 + chopper: ^5.0.0 code_builder: ^4.1.0 dart_style: ^2.0.0 logging: ^1.0.0 diff --git a/example/pubspec.lock b/example/pubspec.lock index 572b2c64..f9411798 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -126,7 +126,7 @@ packages: path: "../chopper" relative: true source: path - version: "4.0.1" + version: "4.1.0" chopper_generator: dependency: "direct dev" description: From 34b3bda917fd187f3c40b21a76d1eca887de5910 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Tue, 13 Sep 2022 15:17:28 +0100 Subject: [PATCH 25/61] Revert analyzer to ^4.1.0 and silence linters for Element.enclosingElement (#354) --- chopper_generator/lib/src/generator.dart | 4 ++++ chopper_generator/pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 13b3c606..4afa6fd3 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -339,8 +339,12 @@ class ChopperGenerator extends GeneratorForAnnotation { }); } + /// TODO: upgrade analyzer to ^4.4.0 to replace enclosingElement with enclosingElement3 + /// https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/CHANGELOG.md#440 String _factoryForFunction(FunctionTypedElement function) => + // ignore: deprecated_member_use function.enclosingElement is ClassElement + // ignore: deprecated_member_use ? '${function.enclosingElement!.name}.${function.name}' : function.name!; diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index a5804be1..588ed462 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.0.0 +version: 5.0.0+1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper @@ -8,7 +8,7 @@ environment: sdk: ">=2.17.0 <3.0.0" dependencies: - analyzer: ">=4.1.0 <4.3.0" + analyzer: ^4.1.0 build: ^2.0.0 built_collection: ^5.0.0 chopper: ^5.0.0 From 426a16e9580f07063b631e9179603805d198422e Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 17 Sep 2022 06:58:15 +0100 Subject: [PATCH 26/61] [chopper_generator] Update analyzer to ^4.4.0 and code_builde to ^4.3.0 and migrate deprecated code (#358) --- chopper/example/definition.chopper.dart | 83 ++- chopper/test/test_service.chopper.dart | 344 ++++++++++--- chopper_generator/analysis_options.yaml | 2 +- chopper_generator/lib/src/generator.dart | 85 ++-- chopper_generator/pubspec.yaml | 4 +- example/lib/built_value_resource.chopper.dart | 33 +- example/lib/json_serializable.chopper.dart | 41 +- example/pubspec.lock | 474 ------------------ 8 files changed, 452 insertions(+), 614 deletions(-) delete mode 100644 example/pubspec.lock diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index f43a754d..bae3d084 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -19,7 +19,11 @@ class _$MyService extends MyService { @override Future> getResource(String id) { final $url = '/resources/${id}'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @@ -31,47 +35,94 @@ class _$MyService extends MyService { 'foo': 'bar', }; - final $request = Request('GET', $url, client.baseUrl, - parameters: $params, headers: $headers); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + headers: $headers, + ); return client.send, Map>($request); } @override Future>>> getListResources() { final $url = '/resources/resources'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client .send>, Map>($request); } @override - Future> postResourceUrlEncoded(String toto, String b) { + Future> postResourceUrlEncoded( + String toto, + String b, + ) { final $url = '/resources/'; - final $body = {'a': toto, 'b': b}; - final $request = Request('POST', $url, client.baseUrl, body: $body); + final $body = { + 'a': toto, + 'b': b, + }; + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @override Future> postResources( - Map a, Map b, String c) { + Map a, + Map b, + String c, + ) { final $url = '/resources/multi'; final $parts = [ - PartValue>('1', a), - PartValue>('2', b), - PartValue('3', c) + PartValue>( + '1', + a, + ), + PartValue>( + '2', + b, + ), + PartValue( + '3', + c, + ), ]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } @override Future> postFile(List bytes) { final $url = '/resources/file'; - final $parts = [PartValue>('file', bytes)]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $parts = [ + PartValue>( + 'file', + bytes, + ) + ]; + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index f1fd6769..b49ddc66 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -17,61 +17,97 @@ class _$HttpTestService extends HttpTestService { final definitionType = HttpTestService; @override - Future> getTest(String id, {required String dynamicHeader}) { + Future> getTest( + String id, { + required String dynamicHeader, + }) { final $url = '/test/get/${id}'; final $headers = { 'test': dynamicHeader, }; - final $request = Request('GET', $url, client.baseUrl, headers: $headers); + final $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); return client.send($request); } @override Future> headTest() { final $url = '/test/head'; - final $request = Request('HEAD', $url, client.baseUrl); + final $request = Request( + 'HEAD', + $url, + client.baseUrl, + ); return client.send($request); } @override Future> optionsTest() { final $url = '/test/options'; - final $request = Request('OPTIONS', $url, client.baseUrl); + final $request = Request( + 'OPTIONS', + $url, + client.baseUrl, + ); return client.send($request); } @override Future>>> getStreamTest() { final $url = '/test/get'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send>, int>($request); } @override Future> getAll() { final $url = '/test'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @override Future> getAllWithTrailingSlash() { final $url = '/test/'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @override - Future> getQueryTest( - {String name = '', int? number, int? def = 42}) { + Future> getQueryTest({ + String name = '', + int? number, + int? def = 42, + }) { final $url = '/test/query'; final $params = { 'name': name, 'int': number, - 'default_value': def + 'default_value': def, }; - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @@ -79,47 +115,84 @@ class _$HttpTestService extends HttpTestService { Future> getQueryMapTest(Map query) { final $url = '/test/query_map'; final $params = query; - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @override - Future> getQueryMapTest2(Map query, - {bool? test}) { + Future> getQueryMapTest2( + Map query, { + bool? test, + }) { final $url = '/test/query_map'; final $params = {'test': test}; $params.addAll(query); - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @override - Future> getQueryMapTest3( - {String name = '', - int? number, - Map filters = const {}}) { + Future> getQueryMapTest3({ + String name = '', + int? number, + Map filters = const {}, + }) { final $url = '/test/query_map'; - final $params = {'name': name, 'number': number}; + final $params = { + 'name': name, + 'number': number, + }; $params.addAll(filters); - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @override - Future> getQueryMapTest4( - {String name = '', int? number, Map? filters}) { + Future> getQueryMapTest4({ + String name = '', + int? number, + Map? filters, + }) { final $url = '/test/query_map'; - final $params = {'name': name, 'number': number}; - $params.addAll(filters ?? {}); - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters ?? const {}); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @override Future> getQueryMapTest5({Map? filters}) { final $url = '/test/query_map'; - final $params = filters ?? {}; - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $params = filters ?? const {}; + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send($request); } @@ -127,7 +200,12 @@ class _$HttpTestService extends HttpTestService { Future> getBody(dynamic body) { final $url = '/test/get_body'; final $body = body; - final $request = Request('GET', $url, client.baseUrl, body: $body); + final $request = Request( + 'GET', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @@ -135,7 +213,12 @@ class _$HttpTestService extends HttpTestService { Future> postTest(String data) { final $url = '/test/post'; final $body = data; - final $request = Request('POST', $url, client.baseUrl, body: $body); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @@ -143,15 +226,28 @@ class _$HttpTestService extends HttpTestService { Future> postStreamTest(Stream> byteStream) { final $url = '/test/post'; final $body = byteStream; - final $request = Request('POST', $url, client.baseUrl, body: $body); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @override - Future> putTest(String test, String data) { + Future> putTest( + String test, + String data, + ) { final $url = '/test/put/${test}'; final $body = data; - final $request = Request('PUT', $url, client.baseUrl, body: $body); + final $request = Request( + 'PUT', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @@ -162,15 +258,28 @@ class _$HttpTestService extends HttpTestService { 'foo': 'bar', }; - final $request = Request('DELETE', $url, client.baseUrl, headers: $headers); + final $request = Request( + 'DELETE', + $url, + client.baseUrl, + headers: $headers, + ); return client.send($request); } @override - Future> patchTest(String id, String data) { + Future> patchTest( + String id, + String data, + ) { final $url = '/test/patch/${id}'; final $body = data; - final $request = Request('PATCH', $url, client.baseUrl, body: $body); + final $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @@ -178,7 +287,12 @@ class _$HttpTestService extends HttpTestService { Future> mapTest(Map map) { final $url = '/test/map'; final $body = map; - final $request = Request('POST', $url, client.baseUrl, body: $body); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); return client.send($request); } @@ -186,9 +300,16 @@ class _$HttpTestService extends HttpTestService { Future> postForm(Map fields) { final $url = '/test/form/body'; final $body = fields; - final $request = Request('POST', $url, client.baseUrl, body: $body); - return client.send($request, - requestConverter: convertForm); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); } @override @@ -199,62 +320,123 @@ class _$HttpTestService extends HttpTestService { }; final $body = fields; - final $request = - Request('POST', $url, client.baseUrl, body: $body, headers: $headers); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); return client.send($request); } @override - Future> postFormFields(String foo, int bar) { + Future> postFormFields( + String foo, + int bar, + ) { final $url = '/test/form/body/fields'; - final $body = {'foo': foo, 'bar': bar}; - final $request = Request('POST', $url, client.baseUrl, body: $body); - return client.send($request, - requestConverter: convertForm); + final $body = { + 'foo': foo, + 'bar': bar, + }; + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); } @override Future> forceJsonTest(Map map) { final $url = '/test/map/json'; final $body = map; - final $request = Request('POST', $url, client.baseUrl, body: $body); - return client.send($request, - requestConverter: customConvertRequest, - responseConverter: customConvertResponse); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: customConvertRequest, + responseConverter: customConvertResponse, + ); } @override Future> postResources( - Map a, Map b) { + Map a, + Map b, + ) { final $url = '/test/multi'; final $parts = [ - PartValue>('1', a), - PartValue>('2', b) + PartValue>( + '1', + a, + ), + PartValue>( + '2', + b, + ), ]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } @override Future> postFile(List bytes) { final $url = '/test/file'; - final $parts = [PartValueFile>('file', bytes)]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $parts = [ + PartValueFile>( + 'file', + bytes, + ) + ]; + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } @override - Future> postMultipartFile(MultipartFile file, - {String? id}) { + Future> postMultipartFile( + MultipartFile file, { + String? id, + }) { final $url = '/test/file'; final $parts = [ - PartValue('id', id), - PartValueFile('file', file) + PartValue( + 'id', + id, + ), + PartValueFile( + 'file', + file, + ), ]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } @@ -262,31 +444,51 @@ class _$HttpTestService extends HttpTestService { Future> postListFiles(List files) { final $url = '/test/files'; final $parts = [ - PartValueFile>('files', files) + PartValueFile>( + 'files', + files, + ) ]; - final $request = - Request('POST', $url, client.baseUrl, parts: $parts, multipart: true); + final $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); return client.send($request); } @override Future fullUrl() { final $url = 'https://test.com'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @override Future>> listString() { final $url = '/test/list/string'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send, String>($request); } @override Future> noBody() { final $url = '/test/no-body'; - final $request = Request('POST', $url, client.baseUrl); + final $request = Request( + 'POST', + $url, + client.baseUrl, + ); return client.send($request); } } diff --git a/chopper_generator/analysis_options.yaml b/chopper_generator/analysis_options.yaml index 57256269..ead6c70c 100644 --- a/chopper_generator/analysis_options.yaml +++ b/chopper_generator/analysis_options.yaml @@ -15,7 +15,7 @@ dart_code_metrics: number-of-arguments: 4 maximum-nesting-level: 5 number-of-parameters: 5 - source-lines-of-code: 200 + source-lines-of-code: 250 metrics-exclude: - test/** rules: diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 4afa6fd3..c5388f33 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -174,11 +174,13 @@ class ChopperGenerator extends GeneratorForAnnotation { ); final List blocks = [ - url.assignFinal(_urlVar).statement, + declareFinal(_urlVar).assign(url).statement, ]; if (queries.isNotEmpty) { - blocks.add(_generateMap(queries).assignFinal(_parametersVar).statement); + blocks.add( + declareFinal(_parametersVar).assign(_generateMap(queries)).statement, + ); } // Build an iterable of all the parameters that are nullable @@ -194,21 +196,20 @@ class ChopperGenerator extends GeneratorForAnnotation { [ // Check if the parameter is nullable optionalNullableParameters.contains(queryMap.keys.first) - ? refer(queryMap.keys.first).ifNullThen(refer('{}')) + ? refer(queryMap.keys.first).ifNullThen(refer('const {}')) : refer(queryMap.keys.first), ], ).statement); } else { blocks.add( - // Check if the parameter is nullable - optionalNullableParameters.contains(queryMap.keys.first) - ? refer(queryMap.keys.first) - .ifNullThen(refer('{}')) - .assignFinal(_parametersVar) - .statement - : refer(queryMap.keys.first) - .assignFinal(_parametersVar) - .statement, + declareFinal(_parametersVar) + .assign( + // Check if the parameter is nullable + optionalNullableParameters.contains(queryMap.keys.first) + ? refer(queryMap.keys.first).ifNullThen(refer('const {}')) + : refer(queryMap.keys.first), + ) + .statement, ); } } @@ -226,11 +227,11 @@ class ChopperGenerator extends GeneratorForAnnotation { if (hasBody) { if (body.isNotEmpty) { blocks.add( - refer(body.keys.first).assignFinal(_bodyVar).statement, + declareFinal(_bodyVar).assign(refer(body.keys.first)).statement, ); } else { blocks.add( - _generateMap(fields).assignFinal(_bodyVar).statement, + declareFinal(_bodyVar).assign(_generateMap(fields)).statement, ); } } @@ -243,7 +244,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ).statement); } else { blocks.add( - refer(fieldMap.keys.first).assignFinal(_bodyVar).statement, + declareFinal(_bodyVar).assign(refer(fieldMap.keys.first)).statement, ); } } @@ -253,19 +254,23 @@ class ChopperGenerator extends GeneratorForAnnotation { bool hasParts = multipart && (parts.isNotEmpty || fileFields.isNotEmpty); if (hasParts) { blocks.add( - _generateList(parts, fileFields).assignFinal(_partsVar).statement, + declareFinal(_partsVar) + .assign(_generateList(parts, fileFields)) + .statement, ); } final bool hasPartMap = multipart && partMap.isNotEmpty; if (hasPartMap) { if (hasParts) { - blocks.add(refer('$_partsVar.addAll').call( - [refer(partMap.keys.first)], - ).statement); + blocks.add( + refer('$_partsVar.addAll').call( + [refer(partMap.keys.first)], + ).statement, + ); } else { blocks.add( - refer(partMap.keys.first).assignFinal(_partsVar).statement, + declareFinal(_partsVar).assign(refer(partMap.keys.first)).statement, ); } } @@ -273,12 +278,16 @@ class ChopperGenerator extends GeneratorForAnnotation { final bool hasFileFilesMap = multipart && fileFieldMap.isNotEmpty; if (hasFileFilesMap) { if (hasParts || hasPartMap) { - blocks.add(refer('$_partsVar.addAll').call( - [refer(fileFieldMap.keys.first)], - ).statement); + blocks.add( + refer('$_partsVar.addAll').call( + [refer(fileFieldMap.keys.first)], + ).statement, + ); } else { blocks.add( - refer(fileFieldMap.keys.first).assignFinal(_partsVar).statement, + declareFinal(_partsVar) + .assign(refer(fileFieldMap.keys.first)) + .statement, ); } } @@ -296,13 +305,19 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } - blocks.add(_generateRequest( - method, - hasBody: hasBody, - useQueries: hasQuery, - useHeaders: headers != null, - hasParts: hasParts, - ).assignFinal(_requestVar).statement); + blocks.add( + declareFinal(_requestVar) + .assign( + _generateRequest( + method, + hasBody: hasBody, + useQueries: hasQuery, + useHeaders: headers != null, + hasParts: hasParts, + ), + ) + .statement, + ); final Map namedArguments = {}; @@ -339,13 +354,9 @@ class ChopperGenerator extends GeneratorForAnnotation { }); } - /// TODO: upgrade analyzer to ^4.4.0 to replace enclosingElement with enclosingElement3 - /// https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/CHANGELOG.md#440 String _factoryForFunction(FunctionTypedElement function) => - // ignore: deprecated_member_use - function.enclosingElement is ClassElement - // ignore: deprecated_member_use - ? '${function.enclosingElement!.name}.${function.name}' + function.enclosingElement3 is ClassElement + ? '${function.enclosingElement3!.name}.${function.name}' : function.name!; Map _getAnnotation(MethodElement method, Type type) { diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 588ed462..93386abb 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -8,11 +8,11 @@ environment: sdk: ">=2.17.0 <3.0.0" dependencies: - analyzer: ^4.1.0 + analyzer: ^4.4.0 build: ^2.0.0 built_collection: ^5.0.0 chopper: ^5.0.0 - code_builder: ^4.1.0 + code_builder: ^4.3.0 dart_style: ^2.0.0 logging: ^1.0.0 meta: ^1.3.0 diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index c092d4b0..24e1b894 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -19,14 +19,22 @@ class _$MyService extends MyService { @override Future> getResource(String id) { final $url = '/resources/${id}/'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @override Future>> getBuiltListResources() { final $url = '/resources/list'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send, Resource>($request); } @@ -37,20 +45,33 @@ class _$MyService extends MyService { 'foo': 'bar', }; - final $request = Request('GET', $url, client.baseUrl, headers: $headers); + final $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); return client.send($request); } @override - Future> newResource(Resource resource, {String? name}) { + Future> newResource( + Resource resource, { + String? name, + }) { final $url = '/resources'; final $headers = { if (name != null) 'name': name, }; final $body = resource; - final $request = - Request('POST', $url, client.baseUrl, body: $body, headers: $headers); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); return client.send($request); } } diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index bd44d2f3..8c6e6fbf 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -19,7 +19,11 @@ class _$MyService extends MyService { @override Future> getResource(String id) { final $url = '/resources/${id}/'; - final $request = Request('GET', $url, client.baseUrl); + final $request = Request( + 'GET', + $url, + client.baseUrl, + ); return client.send($request); } @@ -30,7 +34,12 @@ class _$MyService extends MyService { 'test': 'list', }; - final $request = Request('GET', $url, client.baseUrl, headers: $headers); + final $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); return client.send, Resource>($request); } @@ -38,7 +47,12 @@ class _$MyService extends MyService { Future>> getMapResource(String id) { final $url = '/resources/'; final $params = {'id': id}; - final $request = Request('GET', $url, client.baseUrl, parameters: $params); + final $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); return client.send, Map>($request); } @@ -49,20 +63,33 @@ class _$MyService extends MyService { 'foo': 'bar', }; - final $request = Request('GET', $url, client.baseUrl, headers: $headers); + final $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); return client.send($request); } @override - Future> newResource(Resource resource, {String? name}) { + Future> newResource( + Resource resource, { + String? name, + }) { final $url = '/resources'; final $headers = { if (name != null) 'name': name, }; final $body = resource; - final $request = - Request('POST', $url, client.baseUrl, body: $body, headers: $headers); + final $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); return client.send($request); } } diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index f9411798..00000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,474 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.dartlang.org" - source: hosted - version: "40.0.0" - analyzer: - dependency: "direct main" - description: - name: analyzer - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - url: "https://pub.dartlang.org" - source: hosted - version: "0.10.0" - ansicolor: - dependency: transitive - description: - name: ansicolor - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.2" - build: - dependency: transitive - description: - name: build - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.0" - build_config: - dependency: transitive - description: - name: build_config - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - build_daemon: - dependency: transitive - description: - name: build_daemon - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.9" - build_runner: - dependency: "direct dev" - description: - name: build_runner - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.11" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - url: "https://pub.dartlang.org" - source: hosted - version: "7.2.3" - built_collection: - dependency: "direct main" - description: - name: built_collection - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.1" - built_value: - dependency: "direct main" - description: - name: built_value - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.4" - built_value_generator: - dependency: "direct dev" - description: - name: built_value_generator - url: "https://pub.dartlang.org" - source: hosted - version: "8.4.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - chopper: - dependency: "direct main" - description: - path: "../chopper" - relative: true - source: path - version: "4.1.0" - chopper_generator: - dependency: "direct dev" - description: - path: "../chopper_generator" - relative: true - source: path - version: "4.0.3" - code_builder: - dependency: transitive - description: - name: code_builder - url: "https://pub.dartlang.org" - source: hosted - version: "4.1.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.15.0" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - csslib: - dependency: transitive - description: - name: csslib - url: "https://pub.dartlang.org" - source: hosted - version: "0.17.2" - dart_code_metrics: - dependency: "direct dev" - description: - name: dart_code_metrics - url: "https://pub.dartlang.org" - source: hosted - version: "4.16.0" - dart_style: - dependency: transitive - description: - name: dart_style - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.3" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.2" - fixnum: - dependency: transitive - description: - name: fixnum - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - html: - dependency: transitive - description: - name: html - url: "https://pub.dartlang.org" - source: hosted - version: "0.15.0" - http: - dependency: "direct main" - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.4" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - io: - dependency: transitive - description: - name: io - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.4" - json_annotation: - dependency: "direct main" - description: - name: json_annotation - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.6" - lints: - dependency: "direct dev" - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.11" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.7.0" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.2" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.0" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1+1" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - source_gen: - dependency: transitive - description: - name: source_gen - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.2" - source_helper: - dependency: transitive - description: - name: source_helper - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.2" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - stream_transform: - dependency: transitive - description: - name: stream_transform - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - timing: - dependency: transitive - description: - name: timing - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "5.3.1" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" -sdks: - dart: ">=2.17.0 <3.0.0" From 35fdf8f0534381b187f870b7ee3280bf6874d09d Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 17 Sep 2022 09:05:44 +0100 Subject: [PATCH 27/61] Add Makefiles to streamline development (#357) --- chopper/Makefile | 61 ++++++++++++++++++++++++++++++++++++ chopper_built_value/Makefile | 61 ++++++++++++++++++++++++++++++++++++ chopper_generator/Makefile | 46 +++++++++++++++++++++++++++ example/Makefile | 46 +++++++++++++++++++++++++++ tool/makefile_helpers.sh | 27 ++++++++++++++++ 5 files changed, 241 insertions(+) create mode 100644 chopper/Makefile create mode 100644 chopper_built_value/Makefile create mode 100644 chopper_generator/Makefile create mode 100644 example/Makefile create mode 100644 tool/makefile_helpers.sh diff --git a/chopper/Makefile b/chopper/Makefile new file mode 100644 index 00000000..d52b82d2 --- /dev/null +++ b/chopper/Makefile @@ -0,0 +1,61 @@ +# Makefile + +help: + @printf "%-20s %s\n" "Target" "Description" + @printf "%-20s %s\n" "------" "-----------" + @make -pqR : 2>/dev/null \ + | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ + | sort \ + | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ + | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' + +analyze: + @# Help: Analyze the project's Dart code. + dart analyze --fatal-infos + +check_format: + @# Help: Check the formatting of one or more Dart files. + dart format --output=none --set-exit-if-changed . + +check_outdated: + @# Help: Check which of the project's packages are outdated. + dart pub outdated + +check_style: + @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. + make analyze && make check_format + +code_gen: + @# Help: Run the build system for Dart code generation and modular compilation. + dart run build_runner build --delete-conflicting-outputs + +code_gen_watcher: + @# Help: Run the build system for Dart code generation and modular compilation as a watcher. + dart run build_runner watch --delete-conflicting-outputs + +format: + @# Help: Format one or more Dart files. + dart format . + +install: + @# Help: Install all the project's packages + dart pub get + +sure: + @# Help: Analyze the project's Dart code, check the formatting one or more Dart files and run unit tests for the current project. + make check_style && make tests + +show_test_coverage: + @# Help: Run Dart unit tests for the current project and show the coverage. + dart pub global activate coverage && dart pub global run coverage:test_with_coverage + lcov --remove coverage/lcov.info '**.g.dart' '**.mock.dart' '**.chopper.dart' -o coverage/lcov_without_generated_code.info + genhtml coverage/lcov_without_generated_code.info -o coverage/html + source ../tool/makefile_helpers.sh && open_link "coverage/html/index.html" + +tests: + @# Help: Run Dart unit and widget tests for the current project. + dart test + +upgrade: + @# Help: Upgrade all the project's packages. + dart pub upgrade \ No newline at end of file diff --git a/chopper_built_value/Makefile b/chopper_built_value/Makefile new file mode 100644 index 00000000..d52b82d2 --- /dev/null +++ b/chopper_built_value/Makefile @@ -0,0 +1,61 @@ +# Makefile + +help: + @printf "%-20s %s\n" "Target" "Description" + @printf "%-20s %s\n" "------" "-----------" + @make -pqR : 2>/dev/null \ + | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ + | sort \ + | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ + | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' + +analyze: + @# Help: Analyze the project's Dart code. + dart analyze --fatal-infos + +check_format: + @# Help: Check the formatting of one or more Dart files. + dart format --output=none --set-exit-if-changed . + +check_outdated: + @# Help: Check which of the project's packages are outdated. + dart pub outdated + +check_style: + @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. + make analyze && make check_format + +code_gen: + @# Help: Run the build system for Dart code generation and modular compilation. + dart run build_runner build --delete-conflicting-outputs + +code_gen_watcher: + @# Help: Run the build system for Dart code generation and modular compilation as a watcher. + dart run build_runner watch --delete-conflicting-outputs + +format: + @# Help: Format one or more Dart files. + dart format . + +install: + @# Help: Install all the project's packages + dart pub get + +sure: + @# Help: Analyze the project's Dart code, check the formatting one or more Dart files and run unit tests for the current project. + make check_style && make tests + +show_test_coverage: + @# Help: Run Dart unit tests for the current project and show the coverage. + dart pub global activate coverage && dart pub global run coverage:test_with_coverage + lcov --remove coverage/lcov.info '**.g.dart' '**.mock.dart' '**.chopper.dart' -o coverage/lcov_without_generated_code.info + genhtml coverage/lcov_without_generated_code.info -o coverage/html + source ../tool/makefile_helpers.sh && open_link "coverage/html/index.html" + +tests: + @# Help: Run Dart unit and widget tests for the current project. + dart test + +upgrade: + @# Help: Upgrade all the project's packages. + dart pub upgrade \ No newline at end of file diff --git a/chopper_generator/Makefile b/chopper_generator/Makefile new file mode 100644 index 00000000..dee4bcf7 --- /dev/null +++ b/chopper_generator/Makefile @@ -0,0 +1,46 @@ +# Makefile + +help: + @printf "%-20s %s\n" "Target" "Description" + @printf "%-20s %s\n" "------" "-----------" + @make -pqR : 2>/dev/null \ + | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ + | sort \ + | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ + | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' + +analyze: + @# Help: Analyze the project's Dart code. + dart analyze --fatal-infos + +check_format: + @# Help: Check the formatting of one or more Dart files. + dart format --output=none --set-exit-if-changed . + +check_outdated: + @# Help: Check which of the project's packages are outdated. + dart pub outdated + +check_style: + @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. + make analyze && make check_format + +code_gen: + @# Help: Run the build system for Dart code generation and modular compilation. + dart run build_runner build --delete-conflicting-outputs + +code_gen_watcher: + @# Help: Run the build system for Dart code generation and modular compilation as a watcher. + dart run build_runner watch --delete-conflicting-outputs + +format: + @# Help: Format one or more Dart files. + dart format . + +install: + @# Help: Install all the project's packages + dart pub get + +upgrade: + @# Help: Upgrade all the project's packages. + dart pub upgrade \ No newline at end of file diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 00000000..dee4bcf7 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,46 @@ +# Makefile + +help: + @printf "%-20s %s\n" "Target" "Description" + @printf "%-20s %s\n" "------" "-----------" + @make -pqR : 2>/dev/null \ + | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' \ + | sort \ + | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' \ + | xargs -I _ sh -c 'printf "%-20s " _; make _ -nB | (grep -i "^# Help:" || echo "") | tail -1 | sed "s/^# Help: //g"' + +analyze: + @# Help: Analyze the project's Dart code. + dart analyze --fatal-infos + +check_format: + @# Help: Check the formatting of one or more Dart files. + dart format --output=none --set-exit-if-changed . + +check_outdated: + @# Help: Check which of the project's packages are outdated. + dart pub outdated + +check_style: + @# Help: Analyze the project's Dart code and check the formatting one or more Dart files. + make analyze && make check_format + +code_gen: + @# Help: Run the build system for Dart code generation and modular compilation. + dart run build_runner build --delete-conflicting-outputs + +code_gen_watcher: + @# Help: Run the build system for Dart code generation and modular compilation as a watcher. + dart run build_runner watch --delete-conflicting-outputs + +format: + @# Help: Format one or more Dart files. + dart format . + +install: + @# Help: Install all the project's packages + dart pub get + +upgrade: + @# Help: Upgrade all the project's packages. + dart pub upgrade \ No newline at end of file diff --git a/tool/makefile_helpers.sh b/tool/makefile_helpers.sh new file mode 100644 index 00000000..003ff2e4 --- /dev/null +++ b/tool/makefile_helpers.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# Opens a link with the default browser of OS (It works cross-platform) +# +## You can call it like `open_link balad.ir` to open balad website on your default browser +open_link() { + case "$(uname -s)" in + Darwin) + # macOS + open "$1" + ;; + + Linux) + # Linux: + xdg-open "$1" + ;; + + CYGWIN* | MINGW32* | MSYS* | MINGW*) + # Windows + start "$1" + ;; + + *) + echo 'Not supported OS' + ;; + esac +} From 5a59ebf19cdeb3405d5d6c034c46f2064ed5541f Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 18 Sep 2022 06:07:19 +0100 Subject: [PATCH 28/61] Add Bug Report Github issue template (#359) --- .github/ISSUE_TEMPLATE/bug_report.md | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..a67c8b85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,69 @@ +--- +name: Bug report +about: The application is crashing or throws an exception or something else looks wrong. +title: '' +labels: bug +assignees: '' + +--- + +## Steps to Reproduce + + + +1. Execute `dart run` on the code sample +2. ... +3. ... + +**Expected results:** + +**Actual results:** + +
+Code sample + + + +```dart +``` + +
+ +
+ Logs + + + +``` +``` + + + +``` +``` + + + +``` +``` + +
From fcf347817c6437b4149c67eef53bff8130d6bbd4 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 19 Sep 2022 07:12:38 +0100 Subject: [PATCH 29/61] [chopper_generator] Add types to the generated variables (#360) --- chopper/example/definition.chopper.dart | 33 ++-- chopper/test/test_service.chopper.dart | 149 +++++++++--------- chopper_generator/lib/src/generator.dart | 26 +-- example/lib/built_value_resource.chopper.dart | 22 ++- example/lib/json_serializable.chopper.dart | 31 ++-- 5 files changed, 130 insertions(+), 131 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index bae3d084..884b3470 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -18,8 +18,8 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/${id}'; - final $request = Request( + final String $url = '/resources/${id}'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -29,13 +29,12 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final $url = '/resources/'; - final $params = {'id': id}; - final $headers = { + final String $url = '/resources/'; + final Map $params = {'id': id}; + final Map $headers = { 'foo': 'bar', }; - - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -47,8 +46,8 @@ class _$MyService extends MyService { @override Future>>> getListResources() { - final $url = '/resources/resources'; - final $request = Request( + final String $url = '/resources/resources'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -62,12 +61,12 @@ class _$MyService extends MyService { String toto, String b, ) { - final $url = '/resources/'; + final String $url = '/resources/'; final $body = { 'a': toto, 'b': b, }; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -82,8 +81,8 @@ class _$MyService extends MyService { Map b, String c, ) { - final $url = '/resources/multi'; - final $parts = [ + final String $url = '/resources/multi'; + final List $parts = [ PartValue>( '1', a, @@ -97,7 +96,7 @@ class _$MyService extends MyService { c, ), ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -109,14 +108,14 @@ class _$MyService extends MyService { @override Future> postFile(List bytes) { - final $url = '/resources/file'; - final $parts = [ + final String $url = '/resources/file'; + final List $parts = [ PartValue>( 'file', bytes, ) ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index b49ddc66..01d5757e 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -21,12 +21,11 @@ class _$HttpTestService extends HttpTestService { String id, { required String dynamicHeader, }) { - final $url = '/test/get/${id}'; - final $headers = { + final String $url = '/test/get/${id}'; + final Map $headers = { 'test': dynamicHeader, }; - - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -37,8 +36,8 @@ class _$HttpTestService extends HttpTestService { @override Future> headTest() { - final $url = '/test/head'; - final $request = Request( + final String $url = '/test/head'; + final Request $request = Request( 'HEAD', $url, client.baseUrl, @@ -48,8 +47,8 @@ class _$HttpTestService extends HttpTestService { @override Future> optionsTest() { - final $url = '/test/options'; - final $request = Request( + final String $url = '/test/options'; + final Request $request = Request( 'OPTIONS', $url, client.baseUrl, @@ -59,8 +58,8 @@ class _$HttpTestService extends HttpTestService { @override Future>>> getStreamTest() { - final $url = '/test/get'; - final $request = Request( + final String $url = '/test/get'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -70,8 +69,8 @@ class _$HttpTestService extends HttpTestService { @override Future> getAll() { - final $url = '/test'; - final $request = Request( + final String $url = '/test'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -81,8 +80,8 @@ class _$HttpTestService extends HttpTestService { @override Future> getAllWithTrailingSlash() { - final $url = '/test/'; - final $request = Request( + final String $url = '/test/'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -96,13 +95,13 @@ class _$HttpTestService extends HttpTestService { int? number, int? def = 42, }) { - final $url = '/test/query'; - final $params = { + final String $url = '/test/query'; + final Map $params = { 'name': name, 'int': number, 'default_value': def, }; - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -113,9 +112,9 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest(Map query) { - final $url = '/test/query_map'; - final $params = query; - final $request = Request( + final String $url = '/test/query_map'; + final Map $params = query; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -129,10 +128,10 @@ class _$HttpTestService extends HttpTestService { Map query, { bool? test, }) { - final $url = '/test/query_map'; - final $params = {'test': test}; + final String $url = '/test/query_map'; + final Map $params = {'test': test}; $params.addAll(query); - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -147,13 +146,13 @@ class _$HttpTestService extends HttpTestService { int? number, Map filters = const {}, }) { - final $url = '/test/query_map'; - final $params = { + final String $url = '/test/query_map'; + final Map $params = { 'name': name, 'number': number, }; $params.addAll(filters); - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -168,13 +167,13 @@ class _$HttpTestService extends HttpTestService { int? number, Map? filters, }) { - final $url = '/test/query_map'; - final $params = { + final String $url = '/test/query_map'; + final Map $params = { 'name': name, 'number': number, }; $params.addAll(filters ?? const {}); - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -185,9 +184,9 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest5({Map? filters}) { - final $url = '/test/query_map'; - final $params = filters ?? const {}; - final $request = Request( + final String $url = '/test/query_map'; + final Map $params = filters ?? const {}; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -198,9 +197,9 @@ class _$HttpTestService extends HttpTestService { @override Future> getBody(dynamic body) { - final $url = '/test/get_body'; + final String $url = '/test/get_body'; final $body = body; - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -211,9 +210,9 @@ class _$HttpTestService extends HttpTestService { @override Future> postTest(String data) { - final $url = '/test/post'; + final String $url = '/test/post'; final $body = data; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -224,9 +223,9 @@ class _$HttpTestService extends HttpTestService { @override Future> postStreamTest(Stream> byteStream) { - final $url = '/test/post'; + final String $url = '/test/post'; final $body = byteStream; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -240,9 +239,9 @@ class _$HttpTestService extends HttpTestService { String test, String data, ) { - final $url = '/test/put/${test}'; + final String $url = '/test/put/${test}'; final $body = data; - final $request = Request( + final Request $request = Request( 'PUT', $url, client.baseUrl, @@ -253,12 +252,11 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { - final $url = '/test/delete/${id}'; - final $headers = { + final String $url = '/test/delete/${id}'; + final Map $headers = { 'foo': 'bar', }; - - final $request = Request( + final Request $request = Request( 'DELETE', $url, client.baseUrl, @@ -272,9 +270,9 @@ class _$HttpTestService extends HttpTestService { String id, String data, ) { - final $url = '/test/patch/${id}'; + final String $url = '/test/patch/${id}'; final $body = data; - final $request = Request( + final Request $request = Request( 'PATCH', $url, client.baseUrl, @@ -285,9 +283,9 @@ class _$HttpTestService extends HttpTestService { @override Future> mapTest(Map map) { - final $url = '/test/map'; + final String $url = '/test/map'; final $body = map; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -298,9 +296,9 @@ class _$HttpTestService extends HttpTestService { @override Future> postForm(Map fields) { - final $url = '/test/form/body'; + final String $url = '/test/form/body'; final $body = fields; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -314,13 +312,12 @@ class _$HttpTestService extends HttpTestService { @override Future> postFormUsingHeaders(Map fields) { - final $url = '/test/form/body'; - final $headers = { + final String $url = '/test/form/body'; + final Map $headers = { 'content-type': 'application/x-www-form-urlencoded', }; - final $body = fields; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -335,12 +332,12 @@ class _$HttpTestService extends HttpTestService { String foo, int bar, ) { - final $url = '/test/form/body/fields'; + final String $url = '/test/form/body/fields'; final $body = { 'foo': foo, 'bar': bar, }; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -354,9 +351,9 @@ class _$HttpTestService extends HttpTestService { @override Future> forceJsonTest(Map map) { - final $url = '/test/map/json'; + final String $url = '/test/map/json'; final $body = map; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -374,8 +371,8 @@ class _$HttpTestService extends HttpTestService { Map a, Map b, ) { - final $url = '/test/multi'; - final $parts = [ + final String $url = '/test/multi'; + final List $parts = [ PartValue>( '1', a, @@ -385,7 +382,7 @@ class _$HttpTestService extends HttpTestService { b, ), ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -397,14 +394,14 @@ class _$HttpTestService extends HttpTestService { @override Future> postFile(List bytes) { - final $url = '/test/file'; - final $parts = [ + final String $url = '/test/file'; + final List $parts = [ PartValueFile>( 'file', bytes, ) ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -419,8 +416,8 @@ class _$HttpTestService extends HttpTestService { MultipartFile file, { String? id, }) { - final $url = '/test/file'; - final $parts = [ + final String $url = '/test/file'; + final List $parts = [ PartValue( 'id', id, @@ -430,7 +427,7 @@ class _$HttpTestService extends HttpTestService { file, ), ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -442,14 +439,14 @@ class _$HttpTestService extends HttpTestService { @override Future> postListFiles(List files) { - final $url = '/test/files'; - final $parts = [ + final String $url = '/test/files'; + final List $parts = [ PartValueFile>( 'files', files, ) ]; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, @@ -461,8 +458,8 @@ class _$HttpTestService extends HttpTestService { @override Future fullUrl() { - final $url = 'https://test.com'; - final $request = Request( + final String $url = 'https://test.com'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -472,8 +469,8 @@ class _$HttpTestService extends HttpTestService { @override Future>> listString() { - final $url = '/test/list/string'; - final $request = Request( + final String $url = '/test/list/string'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -483,8 +480,8 @@ class _$HttpTestService extends HttpTestService { @override Future> noBody() { - final $url = '/test/no-body'; - final $request = Request( + final String $url = '/test/no-body'; + final Request $request = Request( 'POST', $url, client.baseUrl, diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index c5388f33..4badfc70 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -174,12 +174,14 @@ class ChopperGenerator extends GeneratorForAnnotation { ); final List blocks = [ - declareFinal(_urlVar).assign(url).statement, + declareFinal(_urlVar, type: refer('String')).assign(url).statement, ]; if (queries.isNotEmpty) { blocks.add( - declareFinal(_parametersVar).assign(_generateMap(queries)).statement, + declareFinal(_parametersVar, type: refer('Map')) + .assign(_generateMap(queries)) + .statement, ); } @@ -202,7 +204,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ).statement); } else { blocks.add( - declareFinal(_parametersVar) + declareFinal(_parametersVar, type: refer('Map')) .assign( // Check if the parameter is nullable optionalNullableParameters.contains(queryMap.keys.first) @@ -254,7 +256,7 @@ class ChopperGenerator extends GeneratorForAnnotation { bool hasParts = multipart && (parts.isNotEmpty || fileFields.isNotEmpty); if (hasParts) { blocks.add( - declareFinal(_partsVar) + declareFinal(_partsVar, type: refer('List')) .assign(_generateList(parts, fileFields)) .statement, ); @@ -270,7 +272,9 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } else { blocks.add( - declareFinal(_partsVar).assign(refer(partMap.keys.first)).statement, + declareFinal(_partsVar, type: refer('List')) + .assign(refer(partMap.keys.first)) + .statement, ); } } @@ -285,7 +289,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } else { blocks.add( - declareFinal(_partsVar) + declareFinal(_partsVar, type: refer('List')) .assign(refer(fileFieldMap.keys.first)) .statement, ); @@ -306,7 +310,7 @@ class ChopperGenerator extends GeneratorForAnnotation { } blocks.add( - declareFinal(_requestVar) + declareFinal(_requestVar, type: refer('Request')) .assign( _generateRequest( method, @@ -589,10 +593,14 @@ class ChopperGenerator extends GeneratorForAnnotation { } }); - codeBuffer.writeln('};'); + codeBuffer.writeln('}'); final String code = codeBuffer.toString(); - return code == '{\n};\n' ? null : Code('final $_headersVar = $code'); + return code == '{\n}\n' + ? null + : declareFinal(_headersVar, type: refer('Map')) + .assign(CodeExpression(Code(code))) + .statement; } } diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index 24e1b894..a68d4b5c 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -18,8 +18,8 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/${id}/'; - final $request = Request( + final String $url = '/resources/${id}/'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -29,8 +29,8 @@ class _$MyService extends MyService { @override Future>> getBuiltListResources() { - final $url = '/resources/list'; - final $request = Request( + final String $url = '/resources/list'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -40,12 +40,11 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final $url = '/resources/'; - final $headers = { + final String $url = '/resources/'; + final Map $headers = { 'foo': 'bar', }; - - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -59,13 +58,12 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final $url = '/resources'; - final $headers = { + final String $url = '/resources'; + final Map $headers = { if (name != null) 'name': name, }; - final $body = resource; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index 8c6e6fbf..e9892bfc 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -18,8 +18,8 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final $url = '/resources/${id}/'; - final $request = Request( + final String $url = '/resources/${id}/'; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -29,12 +29,11 @@ class _$MyService extends MyService { @override Future>> getResources() { - final $url = '/resources/all'; - final $headers = { + final String $url = '/resources/all'; + final Map $headers = { 'test': 'list', }; - - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -45,9 +44,9 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final $url = '/resources/'; - final $params = {'id': id}; - final $request = Request( + final String $url = '/resources/'; + final Map $params = {'id': id}; + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -58,12 +57,11 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final $url = '/resources/'; - final $headers = { + final String $url = '/resources/'; + final Map $headers = { 'foo': 'bar', }; - - final $request = Request( + final Request $request = Request( 'GET', $url, client.baseUrl, @@ -77,13 +75,12 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final $url = '/resources'; - final $headers = { + final String $url = '/resources'; + final Map $headers = { if (name != null) 'name': name, }; - final $body = resource; - final $request = Request( + final Request $request = Request( 'POST', $url, client.baseUrl, From 6b3b8407d99844d89c800e8c517a72ca03c60b57 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 19 Sep 2022 10:33:53 +0100 Subject: [PATCH 30/61] Provide an example using an Isolate Worker Pool with Squadron (#361) --- ...son_serializable_squadron_worker_pool.dart | 164 +++++++++++++++ .../lib/json_decode_service.activator.g.dart | 9 + example/lib/json_decode_service.dart | 21 ++ example/lib/json_decode_service.vm.g.dart | 13 ++ example/lib/json_decode_service.worker.g.dart | 59 ++++++ example/pubspec.yaml | 2 + faq.md | 194 ++++++++++++++++++ 7 files changed, 462 insertions(+) create mode 100644 example/bin/main_json_serializable_squadron_worker_pool.dart create mode 100644 example/lib/json_decode_service.activator.g.dart create mode 100644 example/lib/json_decode_service.dart create mode 100644 example/lib/json_decode_service.vm.g.dart create mode 100644 example/lib/json_decode_service.worker.g.dart diff --git a/example/bin/main_json_serializable_squadron_worker_pool.dart b/example/bin/main_json_serializable_squadron_worker_pool.dart new file mode 100644 index 00000000..2c3bbb08 --- /dev/null +++ b/example/bin/main_json_serializable_squadron_worker_pool.dart @@ -0,0 +1,164 @@ +/// This example uses +/// - https://github.com/google/json_serializable.dart +/// - https://github.com/d-markey/squadron +/// - https://github.com/d-markey/squadron_builder + +import 'dart:async' show FutureOr; +import 'dart:convert' show jsonDecode; + +import 'package:chopper/chopper.dart'; +import 'package:chopper_example/json_decode_service.dart'; +import 'package:chopper_example/json_serializable.dart'; +import 'package:http/testing.dart'; +import 'package:squadron/squadron.dart'; +import 'package:http/http.dart' as http; + +import 'main_json_serializable.dart' show authHeader; + +typedef JsonFactory = T Function(Map json); + +/// This JsonConverter works with or without a WorkerPool +class JsonSerializableWorkerPoolConverter extends JsonConverter { + const JsonSerializableWorkerPoolConverter(this.factories, [this.workerPool]); + + final Map factories; + final JsonDecodeServiceWorkerPool? workerPool; + + T? _decodeMap(Map values) { + /// Get jsonFactory using Type parameters + /// if not found or invalid, throw error or return null + final jsonFactory = factories[T]; + if (jsonFactory == null || jsonFactory is! JsonFactory) { + /// throw serializer not found error; + return null; + } + + return jsonFactory(values); + } + + List _decodeList(Iterable values) => + values.where((v) => v != null).map((v) => _decode(v)).toList(); + + dynamic _decode(entity) { + if (entity is Iterable) return _decodeList(entity as List); + + if (entity is Map) return _decodeMap(entity as Map); + + return entity; + } + + @override + FutureOr> convertResponse( + Response response, + ) async { + // use [JsonConverter] to decode json + final jsonRes = await super.convertResponse(response); + + return jsonRes.copyWith(body: _decode(jsonRes.body)); + } + + @override + FutureOr convertError(Response response) async { + // use [JsonConverter] to decode json + final jsonRes = await super.convertError(response); + + return jsonRes.copyWith( + body: ResourceError.fromJsonFactory(jsonRes.body), + ); + } + + @override + FutureOr tryDecodeJson(String data) async { + try { + // if there is a worker pool use it, otherwise run in the main thread + return workerPool != null + ? await workerPool!.jsonDecode(data) + : jsonDecode(data); + } catch (error) { + print(error); + + chopperLogger.warning(error); + + return data; + } + } +} + +/// Simple client to have working example without remote server +final client = MockClient((http.Request req) async { + if (req.method == 'POST') { + return http.Response('{"type":"Fatal","message":"fatal error"}', 500); + } + if (req.method == 'GET' && req.headers['test'] == 'list') { + return http.Response('[{"id":"1","name":"Foo"}]', 200); + } + + return http.Response('{"id":"1","name":"Foo"}', 200); +}); + +/// inspired by https://github.com/d-markey/squadron_sample/blob/main/lib/main.dart +void initSquadron(String id) { + Squadron.setId(id); + Squadron.setLogger(ConsoleSquadronLogger()); + Squadron.logLevel = SquadronLogLevel.all; + Squadron.debugMode = true; +} + +Future main() async { + /// initialize Squadron before using it + initSquadron('worker_pool_example'); + + final jsonDecodeServiceWorkerPool = JsonDecodeServiceWorkerPool( + // Set whatever you want here + concurrencySettings: ConcurrencySettings.oneCpuThread, + ); + + /// start the Worker Pool + await jsonDecodeServiceWorkerPool.start(); + + final converter = JsonSerializableWorkerPoolConverter( + { + Resource: Resource.fromJsonFactory, + }, + // make sure to provide the WorkerPool to the JsonConverter + jsonDecodeServiceWorkerPool, + ); + + final chopper = ChopperClient( + client: client, + baseUrl: 'http://localhost:8000', + // bind your object factories here + converter: converter, + errorConverter: converter, + services: [ + // the generated service + MyService.create(), + ], + /* ResponseInterceptorFunc | RequestInterceptorFunc | ResponseInterceptor | RequestInterceptor */ + interceptors: [authHeader], + ); + + final myService = chopper.getService(); + + /// All of the calls below will use jsonDecode in an Isolate worker + final response1 = await myService.getResource('1'); + print('response 1: ${response1.body}'); // undecoded String + + final response2 = await myService.getResources(); + print('response 2: ${response2.body}'); // decoded list of Resources + + final response3 = await myService.getTypedResource(); + print('response 3: ${response3.body}'); // decoded Resource + + final response4 = await myService.getMapResource('1'); + print('response 4: ${response4.body}'); // undecoded Resource + + try { + await myService.newResource(Resource('3', 'Super Name')); + } on Response catch (error) { + print(error.body); + } + + /// stop the Worker Pool + jsonDecodeServiceWorkerPool.stop(); +} diff --git a/example/lib/json_decode_service.activator.g.dart b/example/lib/json_decode_service.activator.g.dart new file mode 100644 index 00000000..1756d3d3 --- /dev/null +++ b/example/lib/json_decode_service.activator.g.dart @@ -0,0 +1,9 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// SquadronWorkerGenerator +// ************************************************************************** + +import 'json_decode_service.vm.g.dart'; + +final $JsonDecodeServiceActivator = $getJsonDecodeServiceActivator(); diff --git a/example/lib/json_decode_service.dart b/example/lib/json_decode_service.dart new file mode 100644 index 00000000..41c94785 --- /dev/null +++ b/example/lib/json_decode_service.dart @@ -0,0 +1,21 @@ +/// This example uses https://github.com/d-markey/squadron_builder + +import 'dart:async'; +import 'dart:convert' show json; + +import 'package:squadron/squadron.dart'; +import 'package:squadron/squadron_annotations.dart'; + +import 'json_decode_service.activator.g.dart'; + +part 'json_decode_service.worker.g.dart'; + +@SquadronService( + // disable web to keep the number of generated files low for this example + web: false, +) +class JsonDecodeService extends WorkerService + with $JsonDecodeServiceOperations { + @SquadronMethod() + Future jsonDecode(String source) async => json.decode(source); +} diff --git a/example/lib/json_decode_service.vm.g.dart b/example/lib/json_decode_service.vm.g.dart new file mode 100644 index 00000000..7ad9a226 --- /dev/null +++ b/example/lib/json_decode_service.vm.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// SquadronWorkerGenerator +// ************************************************************************** + +import 'package:squadron/squadron_service.dart'; +import 'json_decode_service.dart'; + +// VM entry point +void _start(Map command) => run($JsonDecodeServiceInitializer, command); + +dynamic $getJsonDecodeServiceActivator() => _start; diff --git a/example/lib/json_decode_service.worker.g.dart b/example/lib/json_decode_service.worker.g.dart new file mode 100644 index 00000000..d50372ab --- /dev/null +++ b/example/lib/json_decode_service.worker.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'json_decode_service.dart'; + +// ************************************************************************** +// SquadronWorkerGenerator +// ************************************************************************** + +// Operations map for JsonDecodeService +mixin $JsonDecodeServiceOperations on WorkerService { + @override + late final Map operations = + _getOperations(this as JsonDecodeService); + + static const int _$jsonDecodeId = 1; + + static Map _getOperations(JsonDecodeService svc) => { + _$jsonDecodeId: (r) => svc.jsonDecode(r.args[0]), + }; +} + +// Service initializer +JsonDecodeService $JsonDecodeServiceInitializer(WorkerRequest startRequest) => + JsonDecodeService(); + +// Worker for JsonDecodeService +class JsonDecodeServiceWorker extends Worker + with $JsonDecodeServiceOperations + implements JsonDecodeService { + JsonDecodeServiceWorker() : super($JsonDecodeServiceActivator); + + @override + Future jsonDecode(String source) => send( + $JsonDecodeServiceOperations._$jsonDecodeId, + args: [source], + token: null, + inspectRequest: false, + inspectResponse: false, + ); + + @override + Map get operations => WorkerService.noOperations; +} + +// Worker pool for JsonDecodeService +class JsonDecodeServiceWorkerPool extends WorkerPool + with $JsonDecodeServiceOperations + implements JsonDecodeService { + JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) + : super(() => JsonDecodeServiceWorker(), + concurrencySettings: concurrencySettings); + + @override + Future jsonDecode(String source) => + execute((w) => w.jsonDecode(source)); + + @override + Map get operations => WorkerService.noOperations; +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9975770b..3bce130d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: analyzer: http: built_collection: + squadron: ^4.3.0 dev_dependencies: build_runner: @@ -22,6 +23,7 @@ dev_dependencies: built_value_generator: dart_code_metrics: ^4.8.1 lints: ^2.0.0 + squadron_builder: ^0.9.0 dependency_overrides: chopper: diff --git a/faq.md b/faq.md index 40658686..fca603c6 100644 --- a/faq.md +++ b/faq.md @@ -169,3 +169,197 @@ interceptors: [ ``` The actual implementation of the algorithm above may vary based on how the backend API - more precisely the login and session handling - of your app looks like. + +## Decoding JSON using Isolates + +Sometimes you want to decode JSON outside the main thread in order to reduce janking. In this example we're going to go +even further and implement a Worker Pool using [Squadron](https://pub.dev/packages/squadron/install) which can +dynamically spawn a maximum number of Workers as they become needed. + +#### Install the dependencies + +- [squadron](https://pub.dev/packages/squadron) +- [squadron_builder](https://pub.dev/packages/squadron_builder) +- [json_annotation](https://pub.dev/packages/json_annotation) +- [json_serializable](https://pub.dev/packages/json_serializable) + +#### Write a JSON decode service + +We'll leverage [squadron_builder](https://pub.dev/packages/squadron_builder) and the power of code generation. + +```dart +import 'dart:async'; +import 'dart:convert' show json; + +import 'package:squadron/squadron.dart'; +import 'package:squadron/squadron_annotations.dart'; + +import 'json_decode_service.activator.g.dart'; + +part 'json_decode_service.worker.g.dart'; + +@SquadronService() +class JsonDecodeService extends WorkerService with $JsonDecodeServiceOperations { + @SquadronMethod() + Future jsonDecode(String source) async => json.decode(source); +} +``` + +Extracted from the [full example here](example/lib/json_decode_service.dart). + +#### Write a custom JsonConverter + +Using [json_serializable](https://pub.dev/packages/json_serializable) we'll create a [JsonConverter](https://github.com/lejard-h/chopper/blob/master/chopper/lib/src/interceptor.dart#L228) +which works with or without a [WorkerPool](https://github.com/d-markey/squadron#features). + +```dart +import 'dart:async' show FutureOr; +import 'dart:convert' show jsonDecode; + +import 'package:chopper/chopper.dart'; +import 'package:chopper_example/json_decode_service.dart'; +import 'package:chopper_example/json_serializable.dart'; + +typedef JsonFactory = T Function(Map json); + +class JsonSerializableWorkerPoolConverter extends JsonConverter { + const JsonSerializableWorkerPoolConverter(this.factories, [this.workerPool]); + + final Map factories; + + /// Make the WorkerPool optional so that the JsonConverter still works without it + final JsonDecodeServiceWorkerPool? workerPool; + + /// By overriding tryDecodeJson we give our JsonConverter + /// the ability to decode JSON in an Isolate. + @override + FutureOr tryDecodeJson(String data) async { + try { + return workerPool != null + ? await workerPool!.jsonDecode(data) + : jsonDecode(data); + } catch (error) { + print(error); + + chopperLogger.warning(error); + + return data; + } + } + + T? _decodeMap(Map values) { + final jsonFactory = factories[T]; + if (jsonFactory == null || jsonFactory is! JsonFactory) { + return null; + } + + return jsonFactory(values); + } + + List _decodeList(Iterable values) => + values.where((v) => v != null).map((v) => _decode(v)).toList(); + + dynamic _decode(entity) { + if (entity is Iterable) return _decodeList(entity as List); + + if (entity is Map) return _decodeMap(entity as Map); + + return entity; + } + + @override + FutureOr> convertResponse( + Response response, + ) async { + final jsonRes = await super.convertResponse(response); + + return jsonRes.copyWith(body: _decode(jsonRes.body)); + } + + @override + FutureOr convertError(Response response) async { + final jsonRes = await super.convertError(response); + + return jsonRes.copyWith( + body: ResourceError.fromJsonFactory(jsonRes.body), + ); + } +} +``` + +Extracted from the [full example here](example/bin/main_json_serializable_squadron_worker_pool.dart). + +#### Code generation + +It goes without saying that running the code generation is a pre-requisite at this stage + +```bash +flutter pub run build_runner build +``` + +#### Configure a WorkerPool and run the example + +```dart +/// inspired by https://github.com/d-markey/squadron_sample/blob/main/lib/main.dart +void initSquadron(String id) { + Squadron.setId(id); + Squadron.setLogger(ConsoleSquadronLogger()); + Squadron.logLevel = SquadronLogLevel.all; + Squadron.debugMode = true; +} + +Future main() async { + /// initialize Squadron before using it + initSquadron('worker_pool_example'); + + final jsonDecodeServiceWorkerPool = JsonDecodeServiceWorkerPool( + // Set whatever you want here + concurrencySettings: ConcurrencySettings.oneCpuThread, + ); + + /// start the Worker Pool + await jsonDecodeServiceWorkerPool.start(); + + /// Instantiate the JsonConverter from above + final converter = JsonSerializableWorkerPoolConverter( + { + Resource: Resource.fromJsonFactory, + }, + /// make sure to provide the WorkerPool to the JsonConverter + jsonDecodeServiceWorkerPool, + ); + + /// Instantiate a ChopperClient + final chopper = ChopperClient( + client: client, + baseUrl: 'http://localhost:8000', + // bind your object factories here + converter: converter, + errorConverter: converter, + services: [ + // the generated service + MyService.create(), + ], + /* ResponseInterceptorFunc | RequestInterceptorFunc | ResponseInterceptor | RequestInterceptor */ + interceptors: [authHeader], + ); + + /// Do stuff with myService + final myService = chopper.getService(); + + /// ...stuff... + + /// stop the Worker Pool once done + jsonDecodeServiceWorkerPool.stop(); +} +``` + +[The full example can be found here](example/bin/main_json_serializable_squadron_worker_pool.dart). + +#### Further reading + +This barely scratches the surface. If you want to know more about [squadron](https://github.com/d-markey/squadron) and +[squadron_builder](https://github.com/d-markey/squadron_builder) make sure to head over to their respective repositories. + +[David Markey](https://github.com/d-markey]), the author of squadron, was kind enough as to provide us with an [excellent Flutter example](https://github.com/d-markey/squadron_builder) using +both packages. \ No newline at end of file From d74790ce226bfadbada024ad6f9ed2191219ddeb Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 8 Oct 2022 16:45:46 +0100 Subject: [PATCH 31/61] mapToQuery changes (#364) --- chopper/lib/src/annotations.dart | 17 ++ chopper/lib/src/request.dart | 20 +- chopper/lib/src/utils.dart | 56 +++-- chopper/test/base_test.dart | 149 +++++++++++- chopper/test/test_service.chopper.dart | 56 +++++ chopper/test/test_service.dart | 20 ++ chopper/test/utils_test.dart | 277 +++++++++++++++++++++++ chopper_generator/analysis_options.yaml | 2 +- chopper_generator/lib/src/generator.dart | 15 +- 9 files changed, 586 insertions(+), 26 deletions(-) create mode 100644 chopper/test/utils_test.dart diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index dafed63b..10784c92 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -161,11 +161,21 @@ class Method { /// Mark the body as optional to suppress warnings during code generation final bool optionalBody; + /// Use brackets [ ] to when encoding + /// + /// - lists + /// hxxp://path/to/script?foo[]=123&foo[]=456&foo[]=789 + /// + /// - maps + /// hxxp://path/to/script?user[name]=john&user[surname]=doe&user[age]=21 + final bool useBrackets; + const Method( this.method, { this.optionalBody = false, this.path = '', this.headers = const {}, + this.useBrackets = false, }); } @@ -176,6 +186,7 @@ class Get extends Method { super.optionalBody = true, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Get); } @@ -188,6 +199,7 @@ class Post extends Method { super.optionalBody, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Post); } @@ -198,6 +210,7 @@ class Delete extends Method { super.optionalBody = true, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Delete); } @@ -210,6 +223,7 @@ class Put extends Method { super.optionalBody, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Put); } @@ -221,6 +235,7 @@ class Patch extends Method { super.optionalBody, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Patch); } @@ -231,6 +246,7 @@ class Head extends Method { super.optionalBody = true, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Head); } @@ -240,6 +256,7 @@ class Options extends Method { super.optionalBody = true, super.path, super.headers, + super.useBrackets, }) : super(HttpMethod.Options); } diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index 8378e276..acd335ad 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; @@ -18,6 +17,7 @@ class Request { final Map parameters; final Map headers; final bool multipart; + final bool useBrackets; const Request( this.method, @@ -28,6 +28,7 @@ class Request { this.headers = const {}, this.multipart = false, this.parts = const [], + this.useBrackets = false, }); /// Makes a copy of this request, replacing original values with the given ones. @@ -37,23 +38,25 @@ class Request { dynamic body, Map? parameters, Map? headers, - Encoding? encoding, List? parts, bool? multipart, String? baseUrl, + bool? useBrackets, }) => Request( (method ?? this.method) as String, url ?? this.url, - baseUrl ?? this.baseUrl, body: body ?? this.body, parameters: parameters ?? this.parameters, headers: headers ?? this.headers, parts: parts ?? this.parts, multipart: multipart ?? this.multipart, + baseUrl ?? this.baseUrl, + useBrackets: useBrackets ?? this.useBrackets, ); - Uri _buildUri() => buildUri(baseUrl, url, parameters); + Uri _buildUri() => + buildUri(baseUrl, url, parameters, useBrackets: useBrackets); Map _buildHeaders() => {...headers}; @@ -110,7 +113,12 @@ class PartValueFile extends PartValue { /// Builds a valid URI from [baseUrl], [url] and [parameters]. /// /// If [url] starts with 'http://' or 'https://', baseUrl is ignored. -Uri buildUri(String baseUrl, String url, Map parameters) { +Uri buildUri( + String baseUrl, + String url, + Map parameters, { + bool useBrackets = false, +}) { // If the request's url is already a fully qualified URL, we can use it // as-is and ignore the baseUrl. Uri uri = url.startsWith('http://') || url.startsWith('https://') @@ -119,7 +127,7 @@ Uri buildUri(String baseUrl, String url, Map parameters) { ? Uri.parse('$baseUrl/$url') : Uri.parse('$baseUrl$url'); - String query = mapToQuery(parameters); + String query = mapToQuery(parameters, useBrackets: useBrackets); if (query.isNotEmpty) { if (uri.hasQuery) { query += '&${uri.query}'; diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index e4d17f7d..85ff8c62 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -56,29 +56,39 @@ final chopperLogger = Logger('Chopper'); /// Creates a valid URI query string from [map]. /// /// E.g., `{'foo': 'bar', 'ints': [ 1337, 42 ] }` will become 'foo=bar&ints=1337&ints=42'. -String mapToQuery(Map map) => _mapToQuery(map).join('&'); +String mapToQuery(Map map, {bool useBrackets = false}) => + _mapToQuery(map, useBrackets: useBrackets).join('&'); Iterable<_Pair> _mapToQuery( Map map, { String? prefix, + bool useBrackets = false, }) { final Set<_Pair> pairs = {}; map.forEach((key, value) { - if (value != null) { - String name = Uri.encodeQueryComponent(key); + String name = Uri.encodeQueryComponent(key); - if (prefix != null) { - name = '$prefix.$name'; - } + if (prefix != null) { + name = useBrackets + ? '$prefix${Uri.encodeQueryComponent('[')}$name${Uri.encodeQueryComponent(']')}' + : '$prefix.$name'; + } + if (value != null) { if (value is Iterable) { - pairs.addAll(_iterableToQuery(name, value)); + pairs.addAll(_iterableToQuery(name, value, useBrackets: useBrackets)); } else if (value is Map) { - pairs.addAll(_mapToQuery(value, prefix: name)); - } else if (value.toString().isNotEmpty) { - pairs.add(_Pair(name, _normalizeValue(value))); + pairs.addAll( + _mapToQuery(value, prefix: name, useBrackets: useBrackets), + ); + } else { + pairs.add( + _Pair(name, _normalizeValue(value)), + ); } + } else { + pairs.add(_Pair(name, '')); } }); @@ -87,20 +97,34 @@ Iterable<_Pair> _mapToQuery( Iterable<_Pair> _iterableToQuery( String name, - Iterable values, -) => - values.map((v) => _Pair(name, _normalizeValue(v))); + Iterable values, { + bool useBrackets = false, +}) => + values.where((value) => value?.toString().isNotEmpty ?? false).map( + (value) => _Pair( + name, + _normalizeValue(value), + useBrackets: useBrackets, + ), + ); -String _normalizeValue(value) => Uri.encodeComponent(value.toString()); +String _normalizeValue(value) => Uri.encodeComponent(value?.toString() ?? ''); class _Pair { final A first; final B second; + final bool useBrackets; - const _Pair(this.first, this.second); + const _Pair( + this.first, + this.second, { + this.useBrackets = false, + }); @override - String toString() => '$first=$second'; + String toString() => useBrackets + ? '$first${Uri.encodeQueryComponent('[]')}=$second' + : '$first=$second'; } bool isTypeOf() => _Instance() is _Instance; diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 99d81231..cb281caf 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -107,7 +107,7 @@ void main() { final httpClient = MockClient((request) async { expect( request.url.toString(), - equals('$baseUrl/test/query'), + equals('$baseUrl/test/query?name=&int=&default_value='), ); expect(request.method, equals('GET')); @@ -129,7 +129,7 @@ void main() { final httpClient = MockClient((request) async { expect( request.url.toString(), - equals('$baseUrl/test/query?default_value=42'), + equals('$baseUrl/test/query?name=&int=&default_value=42'), ); expect(request.method, equals('GET')); @@ -888,4 +888,149 @@ void main() { httpClient.close(); }); + + test('List query param', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/list_query_param' + '?value=foo' + '&value=bar' + '&value=baz'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getUsingListQueryParam([ + 'foo', + 'bar', + 'baz', + ]); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('List query param with brackets', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/list_query_param_with_brackets' + '?value%5B%5D=foo' + '&value%5B%5D=bar' + '&value%5B%5D=baz'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getUsingListQueryParamWithBrackets([ + 'foo', + 'bar', + 'baz', + ]); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Map query param using default dot QueryMapSeparator', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/map_query_param' + '?value.bar=baz' + '&value.zap=abc' + '&value.etc.abc=def' + '&value.etc.ghi=jkl' + '&value.etc.mno.opq=rst' + '&value.etc.mno.uvw=xyz' + '&value.etc.mno.list=a' + '&value.etc.mno.list=123' + '&value.etc.mno.list=false'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getUsingMapQueryParam({ + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'list': ['a', 123, false], + }, + }, + }); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Map query param with brackets QueryMapSeparator', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/map_query_param_with_brackets' + '?value%5Bbar%5D=baz' + '&value%5Bzap%5D=abc' + '&value%5Betc%5D%5Babc%5D=def' + '&value%5Betc%5D%5Bghi%5D=jkl' + '&value%5Betc%5D%5Bmno%5D%5Bopq%5D=rst' + '&value%5Betc%5D%5Bmno%5D%5Buvw%5D=xyz' + '&value%5Betc%5D%5Bmno%5D%5Blist%5D%5B%5D=a' + '&value%5Betc%5D%5Bmno%5D%5Blist%5D%5B%5D=123' + '&value%5Betc%5D%5Bmno%5D%5Blist%5D%5B%5D=false'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = + await service.getUsingMapQueryParamWithBrackets({ + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'list': ['a', 123, false], + }, + }, + }); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index 01d5757e..d0a43b5c 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -488,4 +488,60 @@ class _$HttpTestService extends HttpTestService { ); return client.send($request); } + + @override + Future> getUsingListQueryParam(List value) { + final String $url = '/test/list_query_param'; + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getUsingListQueryParamWithBrackets( + List value) { + final String $url = '/test/list_query_param_with_brackets'; + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + useBrackets: true, + ); + return client.send($request); + } + + @override + Future> getUsingMapQueryParam(Map value) { + final String $url = '/test/map_query_param'; + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getUsingMapQueryParamWithBrackets( + Map value) { + final String $url = '/test/map_query_param_with_brackets'; + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + useBrackets: true, + ); + return client.send($request); + } } diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 03abc239..7789b361 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -138,6 +138,26 @@ abstract class HttpTestService extends ChopperService { @Post(path: 'no-body') Future noBody(); + + @Get(path: '/list_query_param') + Future> getUsingListQueryParam( + @Query('value') List value, + ); + + @Get(path: '/list_query_param_with_brackets', useBrackets: true) + Future> getUsingListQueryParamWithBrackets( + @Query('value') List value, + ); + + @Get(path: '/map_query_param') + Future> getUsingMapQueryParam( + @Query('value') Map value, + ); + + @Get(path: '/map_query_param_with_brackets', useBrackets: true) + Future> getUsingMapQueryParamWithBrackets( + @Query('value') Map value, + ); } Request customConvertRequest(Request req) { diff --git a/chopper/test/utils_test.dart b/chopper/test/utils_test.dart new file mode 100644 index 00000000..649a4651 --- /dev/null +++ b/chopper/test/utils_test.dart @@ -0,0 +1,277 @@ +import 'package:chopper/src/utils.dart'; +import 'package:test/test.dart'; + +void main() { + group('mapToQuery single', () { + , String>{ + {'foo': null}: 'foo=', + {'foo': ''}: 'foo=', + {'foo': ' '}: 'foo=%20', + {'foo': ' '}: 'foo=%20%20', + {'foo': '\t'}: 'foo=%09', + {'foo': '\t\t'}: 'foo=%09%09', + {'foo': 'null'}: 'foo=null', + {'foo': 'bar'}: 'foo=bar', + {'foo': ' bar '}: 'foo=%20bar%20', + {'foo': '\tbar\t'}: 'foo=%09bar%09', + {'foo': '\t\tbar\t\t'}: 'foo=%09%09bar%09%09', + {'foo': 123}: 'foo=123', + {'foo': 0}: 'foo=0', + {'foo': -0.01}: 'foo=-0.01', + {'foo': '0.00'}: 'foo=0.00', + {'foo': 123.456}: 'foo=123.456', + {'foo': 123.450}: 'foo=123.45', + {'foo': -123.456}: 'foo=-123.456', + {'foo': true}: 'foo=true', + {'foo': false}: 'foo=false', + }.forEach((map, query) => + test('$map -> $query', () => expect(mapToQuery(map), query))); + }); + + group('mapToQuery multiple', () { + , String>{ + {'foo': null, 'baz': null}: 'foo=&baz=', + {'foo': '', 'baz': ''}: 'foo=&baz=', + {'foo': null, 'baz': ''}: 'foo=&baz=', + {'foo': '', 'baz': null}: 'foo=&baz=', + {'foo': 'bar', 'baz': ''}: 'foo=bar&baz=', + {'foo': null, 'baz': 'etc'}: 'foo=&baz=etc', + {'foo': '', 'baz': 'etc'}: 'foo=&baz=etc', + {'foo': 'bar', 'baz': 'etc'}: 'foo=bar&baz=etc', + {'foo': 'null', 'baz': 'null'}: 'foo=null&baz=null', + {'foo': ' ', 'baz': ' '}: 'foo=%20&baz=%20', + {'foo': '\t', 'baz': '\t'}: 'foo=%09&baz=%09', + {'foo': 123, 'baz': 456}: 'foo=123&baz=456', + {'foo': 0, 'baz': 0}: 'foo=0&baz=0', + {'foo': '0.00', 'baz': '0.00'}: 'foo=0.00&baz=0.00', + {'foo': 123.456, 'baz': 789.012}: 'foo=123.456&baz=789.012', + {'foo': 123.450, 'baz': 789.010}: 'foo=123.45&baz=789.01', + {'foo': -123.456, 'baz': -789.012}: 'foo=-123.456&baz=-789.012', + {'foo': true, 'baz': true}: 'foo=true&baz=true', + {'foo': false, 'baz': false}: 'foo=false&baz=false', + }.forEach((map, query) => + test('$map -> $query', () => expect(mapToQuery(map), query))); + }); + + group('mapToQuery lists', () { + , String>{ + { + 'foo': ['bar', 'baz', 'etc'], + }: 'foo=bar&foo=baz&foo=etc', + { + 'foo': ['bar', 123, 456.789, 0, -123, -456.789], + }: 'foo=bar&foo=123&foo=456.789&foo=0&foo=-123&foo=-456.789', + { + 'foo': ['', 'baz', 'etc'], + }: 'foo=baz&foo=etc', + { + 'foo': ['bar', '', 'etc'], + }: 'foo=bar&foo=etc', + { + 'foo': ['bar', 'baz', ''], + }: 'foo=bar&foo=baz', + { + 'foo': [null, 'baz', 'etc'], + }: 'foo=baz&foo=etc', + { + 'foo': ['bar', null, 'etc'], + }: 'foo=bar&foo=etc', + { + 'foo': ['bar', 'baz', null], + }: 'foo=bar&foo=baz', + { + 'foo': ['bar', 'baz', ' '], + }: 'foo=bar&foo=baz&foo=%20', + { + 'foo': ['bar', 'baz', '\t'], + }: 'foo=bar&foo=baz&foo=%09', + { + 'foo': ['bar', 'baz', 'etc'], + 'bar': 'baz', + 'etc': '', + 'xyz': null, + }: 'foo=bar&foo=baz&foo=etc&bar=baz&etc=&xyz=', + }.forEach((map, query) => + test('$map -> $query', () => expect(mapToQuery(map), query))); + }); + + group('mapToQuery lists with brackets', () { + , String>{ + { + 'foo': ['bar', 'baz', 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', 123, 456.789, 0, -123, -456.789], + }: 'foo%5B%5D=bar&foo%5B%5D=123&foo%5B%5D=456.789&foo%5B%5D=0&foo%5B%5D=-123&foo%5B%5D=-456.789', + { + 'foo': ['', 'baz', 'etc'], + }: 'foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', '', 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=etc', + { + 'foo': ['bar', 'baz', ''], + }: 'foo%5B%5D=bar&foo%5B%5D=baz', + { + 'foo': [null, 'baz', 'etc'], + }: 'foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', null, 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=etc', + { + 'foo': ['bar', 'baz', null], + }: 'foo%5B%5D=bar&foo%5B%5D=baz', + { + 'foo': ['bar', 'baz', ' '], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=%20', + { + 'foo': ['bar', 'baz', '\t'], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=%09', + { + 'foo': ['bar', 'baz', 'etc'], + 'bar': 'baz', + 'etc': '', + 'xyz': null, + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc&bar=baz&etc=&xyz=', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect( + mapToQuery(map, useBrackets: true), + query, + ), + ), + ); + }); + + group('mapToQuery maps', () { + , String>{ + { + 'foo': {'bar': 'baz'}, + }: 'foo.bar=baz', + { + 'foo': {'bar': ''}, + }: 'foo.bar=', + { + 'foo': {'bar': null}, + }: 'foo.bar=', + { + 'foo': {'bar': ' '}, + }: 'foo.bar=%20', + { + 'foo': {'bar': '\t'}, + }: 'foo.bar=%09', + { + 'foo': {'bar': 'baz', 'etc': 'xyz', 'space': ' ', 'tab': '\t'}, + }: 'foo.bar=baz&foo.etc=xyz&foo.space=%20&foo.tab=%09', + { + 'foo': { + 'bar': 'baz', + 'int': 123, + 'double': 456.789, + 'zero': 0, + 'negInt': -123, + 'negDouble': -456.789, + 'emptyString': '', + 'nullValue': null, + 'space': ' ', + 'tab': '\t', + 'list': ['a', 123, false], + }, + }: 'foo.bar=baz&foo.int=123&foo.double=456.789&foo.zero=0&foo.negInt=-123&foo.negDouble=-456.789&foo.emptyString=&foo.nullValue=&foo.space=%20&foo.tab=%09&foo.list=a&foo.list=123&foo.list=false', + { + 'foo': {'bar': 'baz'}, + 'etc': 'xyz', + }: 'foo.bar=baz&etc=xyz', + { + 'foo': { + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'aab': [ + 'bbc', + 'ccd', + 'eef', + ], + }, + }, + }, + }: 'foo.bar=baz&foo.zap=abc&foo.etc.abc=def&foo.etc.ghi=jkl&foo.etc.mno.opq=rst&foo.etc.mno.uvw=xyz&foo.etc.mno.aab=bbc&foo.etc.mno.aab=ccd&foo.etc.mno.aab=eef', + }.forEach((map, query) => + test('$map -> $query', () => expect(mapToQuery(map), query))); + }); + + group('mapToQuery maps with brackets', () { + , String>{ + { + 'foo': {'bar': 'baz'}, + }: 'foo%5Bbar%5D=baz', + { + 'foo': {'bar': ''}, + }: 'foo%5Bbar%5D=', + { + 'foo': {'bar': null}, + }: 'foo%5Bbar%5D=', + { + 'foo': {'bar': ' '}, + }: 'foo%5Bbar%5D=%20', + { + 'foo': {'bar': '\t'}, + }: 'foo%5Bbar%5D=%09', + { + 'foo': {'bar': 'baz', 'etc': 'xyz', 'space': ' ', 'tab': '\t'}, + }: 'foo%5Bbar%5D=baz&foo%5Betc%5D=xyz&foo%5Bspace%5D=%20&foo%5Btab%5D=%09', + { + 'foo': { + 'bar': 'baz', + 'int': 123, + 'double': 456.789, + 'zero': 0, + 'negInt': -123, + 'negDouble': -456.789, + 'emptyString': '', + 'nullValue': null, + 'space': ' ', + 'tab': '\t', + 'list': ['a', 123, false], + }, + }: 'foo%5Bbar%5D=baz&foo%5Bint%5D=123&foo%5Bdouble%5D=456.789&foo%5Bzero%5D=0&foo%5BnegInt%5D=-123&foo%5BnegDouble%5D=-456.789&foo%5BemptyString%5D=&foo%5BnullValue%5D=&foo%5Bspace%5D=%20&foo%5Btab%5D=%09&foo%5Blist%5D%5B%5D=a&foo%5Blist%5D%5B%5D=123&foo%5Blist%5D%5B%5D=false', + { + 'foo': {'bar': 'baz'}, + 'etc': 'xyz', + }: 'foo%5Bbar%5D=baz&etc=xyz', + { + 'foo': { + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'aab': [ + 'bbc', + 'ccd', + 'eef', + ], + }, + }, + }, + }: 'foo%5Bbar%5D=baz&foo%5Bzap%5D=abc&foo%5Betc%5D%5Babc%5D=def&foo%5Betc%5D%5Bghi%5D=jkl&foo%5Betc%5D%5Bmno%5D%5Bopq%5D=rst&foo%5Betc%5D%5Bmno%5D%5Buvw%5D=xyz&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=bbc&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=ccd&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=eef', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect( + mapToQuery(map, useBrackets: true), + query, + ), + ), + ); + }); +} diff --git a/chopper_generator/analysis_options.yaml b/chopper_generator/analysis_options.yaml index ead6c70c..3a82dc3b 100644 --- a/chopper_generator/analysis_options.yaml +++ b/chopper_generator/analysis_options.yaml @@ -14,7 +14,7 @@ dart_code_metrics: cyclomatic-complexity: 20 number-of-arguments: 4 maximum-nesting-level: 5 - number-of-parameters: 5 + number-of-parameters: 6 source-lines-of-code: 250 metrics-exclude: - test/** diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 4badfc70..6dda3c4a 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -309,6 +309,8 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } + final bool useBrackets = getUseBrackets(method); + blocks.add( declareFinal(_requestVar, type: refer('Request')) .assign( @@ -318,6 +320,7 @@ class ChopperGenerator extends GeneratorForAnnotation { useQueries: hasQuery, useHeaders: headers != null, hasParts: hasParts, + useBrackets: useBrackets, ), ) .statement, @@ -490,6 +493,7 @@ class ChopperGenerator extends GeneratorForAnnotation { bool hasParts = false, bool useQueries = false, bool useHeaders = false, + bool useBrackets = false, }) { final List params = [ literal(getMethodName(method)), @@ -516,6 +520,10 @@ class ChopperGenerator extends GeneratorForAnnotation { namedParams['headers'] = refer(_headersVar); } + if (useBrackets) { + namedParams['useBrackets'] = literalBool(useBrackets); + } + return refer('Request').newInstance(params, namedParams); } @@ -542,7 +550,9 @@ class ChopperGenerator extends GeneratorForAnnotation { ]; list.add(refer( - 'PartValue<${p.type.getDisplayString(withNullability: p.type.isNullable)}>', + 'PartValue<${p.type.getDisplayString( + withNullability: p.type.isNullable, + )}>', ).newInstance(params)); }); fileFields.forEach((p, ConstantReader r) { @@ -618,6 +628,9 @@ String getMethodPath(ConstantReader method) => method.read('path').stringValue; String getMethodName(ConstantReader method) => method.read('method').stringValue; +bool getUseBrackets(ConstantReader method) => + method.peek('useBrackets')?.boolValue ?? false; + extension DartTypeExtension on DartType { bool get isNullable => nullabilitySuffix != NullabilitySuffix.none; } From 0882a7eaa606c640c3fde70f18dc49ed8f2ef039 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Sat, 8 Oct 2022 19:30:46 +0300 Subject: [PATCH 32/61] Version bumped / changelog update (#367) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- example/pubspec.yaml | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index e78e1909..e3d9a080 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.0.1 + +- mapToQuery changes + ## 5.0.0 - API breaking changes (FutureOr) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 96817bb9..9033bc29 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.0.0 +version: 5.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 22074e42..67f4b0e1 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.0.1 + +- Types added + ## 5.0.0 - API breaking changes (FutureOr usage) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 93386abb..2fedb49a 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.0.0+1 +version: 5.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3bce130d..65be75a6 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_example description: Example usage of the Chopper package -version: 0.0.1 +version: 0.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard From e3fd6237f45f74251211b263281bd9cb36bf6c46 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Thu, 13 Oct 2022 17:32:33 +0100 Subject: [PATCH 33/61] Request extends http.BaseRequest (#370) --- chopper/lib/chopper.dart | 1 + chopper/lib/src/annotations.dart | 7 +- chopper/lib/src/base.dart | 15 +- chopper/lib/src/constants.dart | 2 +- chopper/lib/src/extensions.dart | 27 ++ chopper/lib/src/interceptor.dart | 13 +- chopper/lib/src/request.dart | 302 +++++++++---------- chopper/pubspec.yaml | 7 +- chopper/test/base_test.dart | 118 +++++--- chopper/test/extensions_test.dart | 66 ++++ chopper/test/interceptors_test.dart | 4 +- chopper/test/multipart_test.dart | 57 ++-- chopper/test/request_test.dart | 175 +++++++++++ chopper_built_value/test/converter_test.dart | 12 +- 14 files changed, 561 insertions(+), 245 deletions(-) create mode 100644 chopper/lib/src/extensions.dart create mode 100644 chopper/test/extensions_test.dart create mode 100644 chopper/test/request_test.dart diff --git a/chopper/lib/chopper.dart b/chopper/lib/chopper.dart index c004d675..e7230361 100644 --- a/chopper/lib/chopper.dart +++ b/chopper/lib/chopper.dart @@ -7,6 +7,7 @@ export 'src/annotations.dart'; export 'src/authenticator.dart'; export 'src/base.dart'; export 'src/constants.dart'; +export 'src/extensions.dart'; export 'src/interceptor.dart'; export 'src/request.dart'; export 'src/response.dart'; diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index 10784c92..5d850deb 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -1,11 +1,10 @@ import 'dart:async'; +import 'package:chopper/src/constants.dart'; +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; import 'package:meta/meta.dart'; -import 'constants.dart'; -import 'request.dart'; -import 'response.dart'; - /// Defines a Chopper API. /// /// Must be used on an abstract class that extends the [ChopperService] class. diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 3fa4e998..4e594584 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -1,16 +1,15 @@ import 'dart:async'; +import 'package:chopper/src/annotations.dart'; +import 'package:chopper/src/authenticator.dart'; +import 'package:chopper/src/constants.dart'; +import 'package:chopper/src/interceptor.dart'; +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; +import 'package:chopper/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; -import 'annotations.dart'; -import 'authenticator.dart'; -import 'constants.dart'; -import 'interceptor.dart'; -import 'request.dart'; -import 'response.dart'; -import 'utils.dart'; - Type _typeOf() => T; @visibleForTesting diff --git a/chopper/lib/src/constants.dart b/chopper/lib/src/constants.dart index e7e8faec..52db96c8 100644 --- a/chopper/lib/src/constants.dart +++ b/chopper/lib/src/constants.dart @@ -7,7 +7,7 @@ const String formEncodedHeaders = 'application/x-www-form-urlencoded'; // Represent the header for a json api response https://jsonapi.org/#mime-types const String jsonApiHeaders = 'application/vnd.api+json'; -class HttpMethod { +abstract class HttpMethod { static const String Get = 'GET'; static const String Post = 'POST'; static const String Put = 'PUT'; diff --git a/chopper/lib/src/extensions.dart b/chopper/lib/src/extensions.dart new file mode 100644 index 00000000..8d5586c2 --- /dev/null +++ b/chopper/lib/src/extensions.dart @@ -0,0 +1,27 @@ +extension StripStringExtension on String { + /// The string without any leading whitespace and optional [character] + String leftStrip([String? character]) { + final String trimmed = trimLeft(); + + if (character != null && trimmed.startsWith(character)) { + return trimmed.substring(1); + } + + return trimmed; + } + + /// The string without any trailing whitespace and optional [character] + String rightStrip([String? character]) { + final String trimmed = trimRight(); + + if (character != null && trimmed.endsWith(character)) { + return trimmed.substring(0, trimmed.length - 1); + } + + return trimmed; + } + + /// The string without any leading and trailing whitespace and optional [character] + String strip([String? character]) => + character != null ? leftStrip(character).rightStrip(character) : trim(); +} diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 9d393afc..e5f7bbe0 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -1,14 +1,13 @@ import 'dart:async'; import 'dart:convert'; +import 'package:chopper/src/constants.dart'; +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; +import 'package:chopper/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; -import 'constants.dart'; -import 'request.dart'; -import 'response.dart'; -import 'utils.dart'; - /// An interface for implementing response interceptors. /// /// [ResponseInterceptor]s are called after [Converter.convertResponse]. @@ -172,7 +171,7 @@ class HttpLoggingInterceptor @override FutureOr onRequest(Request request) async { final http.BaseRequest base = await request.toBaseRequest(); - chopperLogger.info('--> ${base.method} ${base.url}'); + chopperLogger.info('--> ${base.method} ${base.url.toString()}'); base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); String bytes = ''; @@ -192,7 +191,7 @@ class HttpLoggingInterceptor @override FutureOr onResponse(Response response) { final http.BaseRequest? base = response.base.request; - chopperLogger.info('<-- ${response.statusCode} ${base!.url}'); + chopperLogger.info('<-- ${response.statusCode} ${base!.url.toString()}'); response.base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index acd335ad..b515ec24 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -1,64 +1,110 @@ import 'dart:async'; +import 'package:chopper/src/extensions.dart'; +import 'package:chopper/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; -import 'constants.dart'; -import 'utils.dart'; - /// This class represents an HTTP request that can be made with Chopper. -@immutable -class Request { - final String method; - final String baseUrl; - final String url; +class Request extends http.BaseRequest { + final String path; + final String origin; final dynamic body; - final List parts; final Map parameters; - final Map headers; final bool multipart; + final List parts; final bool useBrackets; - const Request( - this.method, - this.url, - this.baseUrl, { + Request( + String method, + this.path, + this.origin, { this.body, this.parameters = const {}, - this.headers = const {}, + Map headers = const {}, + this.multipart = false, + this.parts = const [], + this.useBrackets = false, + }) : super( + method, + buildUri(origin, path, parameters, useBrackets: useBrackets), + ) { + this.headers.addAll(headers); + } + + /// Build the Chopper [Request] using a [Uri] instead of a [path] and [origin]. + /// Both the query parameters in the [Uri] and those provided explicitly in + /// the [parameters] are merged together. + Request.uri( + String method, + Uri url, { + this.body, + Map? parameters, + Map headers = const {}, this.multipart = false, this.parts = const [], this.useBrackets = false, - }); + }) : origin = url.origin, + path = url.path, + parameters = {...url.queryParametersAll, ...?parameters}, + super( + method, + buildUri( + url.origin, + url.path, + {...url.queryParametersAll, ...?parameters}, + useBrackets: useBrackets, + ), + ) { + this.headers.addAll(headers); + } - /// Makes a copy of this request, replacing original values with the given ones. + /// Makes a copy of this [Request], replacing original values with the given ones. Request copyWith({ - HttpMethod? method, - String? url, + String? method, + String? path, + String? origin, dynamic body, Map? parameters, Map? headers, - List? parts, bool? multipart, - String? baseUrl, + List? parts, bool? useBrackets, }) => Request( - (method ?? this.method) as String, - url ?? this.url, + method ?? this.method, + path ?? this.path, + origin ?? this.origin, body: body ?? this.body, parameters: parameters ?? this.parameters, headers: headers ?? this.headers, - parts: parts ?? this.parts, multipart: multipart ?? this.multipart, - baseUrl ?? this.baseUrl, + parts: parts ?? this.parts, useBrackets: useBrackets ?? this.useBrackets, ); - Uri _buildUri() => - buildUri(baseUrl, url, parameters, useBrackets: useBrackets); - - Map _buildHeaders() => {...headers}; + /// Builds a valid URI from [baseUrl], [url] and [parameters]. + /// + /// If [url] starts with 'http://' or 'https://', baseUrl is ignored. + @visibleForTesting + static Uri buildUri( + String baseUrl, + String url, + Map parameters, { + bool useBrackets = false, + }) { + // If the request's url is already a fully qualified URL, we can use it + // as-is and ignore the baseUrl. + final Uri uri = url.startsWith('http://') || url.startsWith('https://') + ? Uri.parse(url) + : Uri.parse('${baseUrl.strip('/')}/${url.leftStrip('/')}'); + + final String query = mapToQuery(parameters, useBrackets: useBrackets); + + return query.isNotEmpty + ? uri.replace(query: uri.hasQuery ? '${uri.query}&$query' : query) + : uri; + } /// Converts this Chopper Request into a [http.BaseRequest]. /// @@ -69,18 +115,86 @@ class Request { /// - [http.MultipartRequest] if [multipart] is true /// - or a [http.Request] Future toBaseRequest() async { - final Uri uri = _buildUri(); - final Map heads = _buildHeaders(); + if (body is Stream>) return toStreamedRequest(body); + + if (multipart) return toMultipartRequest(); - if (body is Stream>) { - return toStreamedRequest(body, method, uri, heads); + return toHttpRequest(); + } + + /// Convert this [Request] to a [http.Request] + @visibleForTesting + http.Request toHttpRequest() { + final http.Request request = http.Request(method, url) + ..headers.addAll(headers); + + if (body != null) { + if (body is String) { + request.body = body; + } else if (body is List) { + request.bodyBytes = body; + } else if (body is Map) { + request.bodyFields = body; + } else { + throw ArgumentError.value('$body', 'body'); + } } - if (multipart) { - return toMultipartRequest(parts, method, uri, heads); + return request; + } + + /// Convert this [Request] to a [http.MultipartRequest] + @visibleForTesting + Future toMultipartRequest() async { + final http.MultipartRequest request = http.MultipartRequest(method, url) + ..headers.addAll(headers); + + for (final PartValue part in parts) { + if (part.value == null) continue; + + if (part.value is http.MultipartFile) { + request.files.add(part.value); + } else if (part.value is Iterable) { + request.files.addAll(part.value); + } else if (part is PartValueFile) { + if (part.value is List) { + request.files.add( + http.MultipartFile.fromBytes(part.name, part.value), + ); + } else if (part.value is String) { + request.files.add( + await http.MultipartFile.fromPath(part.name, part.value), + ); + } else { + throw ArgumentError( + 'Type ${part.value.runtimeType} is not a supported type for PartFile' + 'Please use one of the following types' + ' - List' + ' - String (path of your file) ' + ' - MultipartFile (from package:http)', + ); + } + } else { + request.fields[part.name] = part.value.toString(); + } } - return toHttpRequest(body, method, uri, heads); + return request; + } + + /// Convert this [Request] to a [http.StreamedRequest] + @visibleForTesting + http.StreamedRequest toStreamedRequest(Stream> bodyStream) { + final http.StreamedRequest request = http.StreamedRequest(method, url) + ..headers.addAll(headers); + + bodyStream.listen( + request.sink.add, + onDone: request.sink.close, + onError: request.sink.addError, + ); + + return request; } } @@ -109,119 +223,3 @@ class PartValue { class PartValueFile extends PartValue { const PartValueFile(super.name, super.value); } - -/// Builds a valid URI from [baseUrl], [url] and [parameters]. -/// -/// If [url] starts with 'http://' or 'https://', baseUrl is ignored. -Uri buildUri( - String baseUrl, - String url, - Map parameters, { - bool useBrackets = false, -}) { - // If the request's url is already a fully qualified URL, we can use it - // as-is and ignore the baseUrl. - Uri uri = url.startsWith('http://') || url.startsWith('https://') - ? Uri.parse(url) - : !baseUrl.endsWith('/') && !url.startsWith('/') - ? Uri.parse('$baseUrl/$url') - : Uri.parse('$baseUrl$url'); - - String query = mapToQuery(parameters, useBrackets: useBrackets); - if (query.isNotEmpty) { - if (uri.hasQuery) { - query += '&${uri.query}'; - } - - return uri.replace(query: query); - } - - return uri; -} - -@visibleForTesting -Future toHttpRequest( - body, - String method, - Uri uri, - Map headers, -) async { - final http.Request baseRequest = http.Request(method, uri) - ..headers.addAll(headers); - - if (body != null) { - if (body is String) { - baseRequest.body = body; - } else if (body is List) { - baseRequest.bodyBytes = body; - } else if (body is Map) { - baseRequest.bodyFields = body; - } else { - throw ArgumentError.value('$body', 'body'); - } - } - - return baseRequest; -} - -@visibleForTesting -Future toMultipartRequest( - List parts, - String method, - Uri uri, - Map headers, -) async { - final http.MultipartRequest baseRequest = http.MultipartRequest(method, uri) - ..headers.addAll(headers); - - for (final PartValue part in parts) { - if (part.value == null) continue; - - if (part.value is http.MultipartFile) { - baseRequest.files.add(part.value); - } else if (part.value is Iterable) { - baseRequest.files.addAll(part.value); - } else if (part is PartValueFile) { - if (part.value is List) { - baseRequest.files.add( - http.MultipartFile.fromBytes(part.name, part.value), - ); - } else if (part.value is String) { - baseRequest.files.add( - await http.MultipartFile.fromPath(part.name, part.value), - ); - } else { - throw ArgumentError( - 'Type ${part.value.runtimeType} is not a supported type for PartFile' - 'Please use one of the following types' - ' - List' - ' - String (path of your file) ' - ' - MultipartFile (from package:http)', - ); - } - } else { - baseRequest.fields[part.name] = part.value.toString(); - } - } - - return baseRequest; -} - -@visibleForTesting -Future toStreamedRequest( - Stream> bodyStream, - String method, - Uri uri, - Map headers, -) async { - final http.StreamedRequest req = http.StreamedRequest(method, uri) - ..headers.addAll(headers); - - bodyStream.listen( - req.sink.add, - onDone: req.sink.close, - onError: req.sink.addError, - ); - - return req; -} diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 9033bc29..68122b13 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -9,16 +9,17 @@ environment: dependencies: http: ">=0.13.0 <1.0.0" - meta: ^1.3.0 logging: ^1.0.0 + meta: ^1.3.0 dev_dependencies: - test: ^1.16.4 build_runner: ^2.0.0 build_test: ^2.0.0 + collection: ^1.16.0 coverage: ^1.0.2 - http_parser: ^4.0.0 dart_code_metrics: ^4.8.1 + http_parser: ^4.0.0 lints: ^2.0.0 + test: ^1.16.4 chopper_generator: path: ../chopper_generator diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index cb281caf..ffdd5d9d 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: long-method + import 'dart:async'; import 'dart:convert'; @@ -49,6 +51,7 @@ void main() { ); } }); + test('GET', () async { final httpClient = MockClient((request) async { expect( @@ -467,58 +470,86 @@ void main() { }); test('url concatenation', () async { - final url1 = buildUri('foo', 'bar', {}); - expect(url1.toString(), equals('foo/bar')); + expect( + Request.buildUri('foo', 'bar', {}).toString(), + equals('foo/bar'), + ); - final url2 = buildUri('foo/', 'bar', {}); - expect(url2.toString(), equals('foo/bar')); + expect( + Request.buildUri('foo/', 'bar', {}).toString(), + equals('foo/bar'), + ); - final url3 = buildUri('foo', '/bar', {}); - expect(url3.toString(), equals('foo/bar')); + expect( + Request.buildUri('foo', '/bar', {}).toString(), + equals('foo/bar'), + ); - final url4 = buildUri('foo/', '/bar', {}); - expect(url4.toString(), equals('foo//bar')); + expect( + Request.buildUri('foo/', '/bar', {}).toString(), + equals('foo/bar'), + ); - final url5 = buildUri('http://foo', '/bar', {}); - expect(url5.toString(), equals('http://foo/bar')); + expect( + Request.buildUri('http://foo', '/bar', {}).toString(), + equals('http://foo/bar'), + ); - final url6 = buildUri('https://foo', '/bar', {}); - expect(url6.toString(), equals('https://foo/bar')); + expect( + Request.buildUri('https://foo', '/bar', {}).toString(), + equals('https://foo/bar'), + ); - final url7 = buildUri('https://foo/', '/bar', {}); - expect(url7.toString(), equals('https://foo//bar')); + expect( + Request.buildUri('https://foo/', '/bar', {}).toString(), + equals('https://foo/bar'), + ); + + expect( + Request.buildUri('https://foo/', '/bar', {'abc': 'xyz'}).toString(), + equals('https://foo/bar?abc=xyz'), + ); + + expect( + Request.buildUri( + 'https://foo/', + '/bar?first=123&second=456', + { + 'third': '789', + 'fourth': '012', + }, + ).toString(), + equals('https://foo/bar?first=123&second=456&third=789&fourth=012'), + ); }); - test('BodyBytes', () async { - final request = await toHttpRequest( - [1, 2, 3], + test('BodyBytes', () { + final request = Request.uri( HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + Uri.parse('https://foo/'), + body: [1, 2, 3], + ).toHttpRequest(); expect(request.bodyBytes, equals([1, 2, 3])); }); - test('BodyFields', () async { - final request = await toHttpRequest( - {'foo': 'bar'}, + test('BodyFields', () { + final request = Request.uri( HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + Uri.parse('https://foo/'), + body: {'foo': 'bar'}, + ).toHttpRequest(); expect(request.bodyFields, equals({'foo': 'bar'})); }); - test('Wrong body', () async { + test('Wrong body', () { try { - await toHttpRequest( - {'foo': 42}, + Request.uri( HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + Uri.parse('https://foo/'), + body: {'foo': 42}, + ).toHttpRequest(); } on ArgumentError catch (e) { expect(e.toString(), equals('Invalid argument (body): "{foo: 42}"')); } @@ -767,7 +798,7 @@ void main() { chopper.onRequest.listen((request) { expect( request.url.toString(), - equals('/test/get/1234'), + equals('$baseUrl/test/get/1234'), ); }); @@ -878,15 +909,18 @@ void main() { final chopper = buildClient(httpClient); final service = chopper.getService(); - try { - await service - .getTest('1234', dynamicHeader: '') - .timeout(const Duration(seconds: 3)); - } catch (e) { - expect(e is TimeoutException, isTrue); - } - - httpClient.close(); + expect( + () async { + try { + await service + .getTest('1234', dynamicHeader: '') + .timeout(const Duration(seconds: 3)); + } finally { + httpClient.close(); + } + }, + throwsA(isA()), + ); }); test('List query param', () async { diff --git a/chopper/test/extensions_test.dart b/chopper/test/extensions_test.dart new file mode 100644 index 00000000..14de4b03 --- /dev/null +++ b/chopper/test/extensions_test.dart @@ -0,0 +1,66 @@ +// ignore_for_file: long-method + +import 'package:chopper/src/extensions.dart'; +import 'package:test/test.dart'; + +void main() { + group('String.leftStrip', () { + test('leftStrip without character any leading whitespace', () { + expect('/foo'.leftStrip(), '/foo'); + expect(' /foo'.leftStrip(), '/foo'); + expect('/foo '.leftStrip(), '/foo '); + expect(' /foo '.leftStrip(), '/foo '); + }); + + test( + 'leftStrip with character removes single leading character and any leading whitespace', + () { + expect('/foo'.leftStrip('/'), 'foo'); + expect('//foo'.leftStrip('/'), '/foo'); + expect(' /foo'.leftStrip('/'), 'foo'); + expect('/foo '.leftStrip('/'), 'foo '); + expect(' /foo '.leftStrip('/'), 'foo '); + }, + ); + }); + + group('String.rightStrip', () { + test('rightStrip without character any trailing whitespace', () { + expect('foo/'.rightStrip(), 'foo/'); + expect(' foo/'.rightStrip(), ' foo/'); + expect('foo/ '.rightStrip(), 'foo/'); + expect(' foo/ '.rightStrip(), ' foo/'); + }); + + test( + 'rightStrip with character removes single trailing character and any trailing whitespace', + () { + expect('foo/'.rightStrip('/'), 'foo'); + expect('foo//'.rightStrip('/'), 'foo/'); + expect(' foo/'.rightStrip('/'), ' foo'); + expect('foo/ '.rightStrip('/'), 'foo'); + expect(' foo/ '.rightStrip('/'), ' foo'); + }, + ); + }); + + group('String.strip', () { + test('strip without character any leading and trailing whitespace', () { + expect('/foo/'.strip(), '/foo/'); + expect(' /foo/'.strip(), '/foo/'); + expect('/foo/ '.strip(), '/foo/'); + expect(' /foo/ '.strip(), '/foo/'); + }); + + test( + 'strip with character removes single leading and trailing character and any leading and trailing whitespace', + () { + expect('/foo/'.strip('/'), 'foo'); + expect('//foo//'.strip('/'), '/foo/'); + expect(' /foo/'.strip('/'), 'foo'); + expect('/foo/ '.strip('/'), 'foo'); + expect(' /foo/ '.strip('/'), 'foo'); + }, + ); + }); +} diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index 4650d89c..16257dca 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -48,7 +48,7 @@ void main() { final chopper = ChopperClient( interceptors: [ (Request request) => - request.copyWith(url: '${request.url}/intercept'), + request.copyWith(path: '${request.url}/intercept'), ], services: [ HttpTestService.create(), @@ -271,7 +271,7 @@ class ResponseIntercept implements ResponseInterceptor { class RequestIntercept implements RequestInterceptor { @override FutureOr onRequest(Request request) => - request.copyWith(url: '${request.url}/intercept'); + request.copyWith(path: '${request.url}/intercept'); } class _Intercepted { diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index 20d28044..1e461d2f 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -203,15 +203,14 @@ void main() { }); test('PartValue', () async { - final req = await toMultipartRequest( - [ + final req = await Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + parts: [ PartValue('foo', 'bar'), PartValue('int', 42), ], - HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + ).toMultipartRequest(); expect(req.fields['foo'], equals('bar')); expect(req.fields['int'], equals('42')); @@ -220,15 +219,14 @@ void main() { test( 'PartFile', () async { - final req = await toMultipartRequest( - [ + final req = await Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + parts: [ PartValueFile('foo', 'test/multipart_test.dart'), PartValueFile>('int', [1, 2]), ], - HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + ).toMultipartRequest(); expect( req.files.firstWhere((f) => f.field == 'foo').filename, @@ -259,17 +257,16 @@ void main() { }); test('Multipart request non nullable', () async { - final req = await toMultipartRequest( - [ + final req = await Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + parts: [ PartValue('int', 42), PartValueFile>('list int', [1, 2]), PartValue('null value', null), PartValueFile('null file', null), ], - HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + ).toMultipartRequest(); expect(req.fields.length, equals(1)); expect(req.fields['int'], equals('42')); @@ -279,8 +276,10 @@ void main() { }); test('PartValue with MultipartFile directly', () async { - final req = await toMultipartRequest( - [ + final req = await Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + parts: [ PartValue( '', http.MultipartFile.fromBytes( @@ -298,10 +297,7 @@ void main() { ), ), ], - HttpMethod.Post, - Uri.parse('/foo'), - {}, - ); + ).toMultipartRequest(); final first = req.files[0]; final second = req.files[1]; @@ -315,4 +311,17 @@ void main() { bytes = await second.finalize().first; expect(bytes, equals([2, 1])); }); + + test('Throw exception', () async { + expect( + () async => await Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + parts: [ + PartValueFile('', 123), + ], + ).toMultipartRequest(), + throwsA(isA()), + ); + }); } diff --git a/chopper/test/request_test.dart b/chopper/test/request_test.dart new file mode 100644 index 00000000..6f886afd --- /dev/null +++ b/chopper/test/request_test.dart @@ -0,0 +1,175 @@ +// ignore_for_file: long-method + +import 'package:chopper/chopper.dart'; +import 'package:test/test.dart'; +import 'package:http/http.dart' as http; +import 'package:collection/collection.dart'; + +void main() { + group('Request', () { + test('constructor produces a BaseRequest', () { + expect( + Request('GET', '/bar', 'https://foo/'), + isA(), + ); + }); + + test('method gets preserved in BaseRequest', () { + expect( + Request('GET', '/bar', 'https://foo/').method, + equals('GET'), + ); + }); + + test('url is correctly parsed and set in BaseRequest', () { + expect( + Request('GET', '/bar', 'https://foo/').url, + equals(Uri.parse('https://foo/bar')), + ); + + expect( + Request('GET', '/bar?lorem=ipsum&dolor=123', 'https://foo/').url, + equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), + ); + + expect( + Request( + 'GET', + '/bar', + 'https://foo/', + parameters: { + 'lorem': 'ipsum', + 'dolor': 123, + }, + ).url, + equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), + ); + + expect( + Request( + 'GET', + '/bar?first=sit&second=amet&first_list=a&first_list=b', + 'https://foo/', + parameters: { + 'lorem': 'ipsum', + 'dolor': 123, + 'second_list': ['a', 'b'], + }, + ).url, + equals(Uri.parse( + 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b&lorem=ipsum&dolor=123&second_list=a&second_list=b', + )), + ); + }); + + test('headers are preserved in BaseRequest', () { + final Map headers = { + 'content-type': 'application/json; charset=utf-8', + 'accept': 'application/json; charset=utf-8', + }; + + final Request request = Request( + 'GET', + '/bar', + 'https://foo/', + headers: headers, + ); + + expect( + MapEquality().equals(request.headers, headers), + true, + ); + }); + + test('copyWith creates a BaseRequest', () { + expect( + Request('GET', '/bar', 'https://foo/').copyWith(method: HttpMethod.Put), + isA(), + ); + }); + }); + + group('Request.uri', () { + test('constructor produces a BaseRequest', () { + expect( + Request.uri('GET', Uri.parse('https://foo/bar')), + isA(), + ); + }); + + test('method gets preserved in BaseRequest', () { + expect( + Request.uri('GET', Uri.parse('https://foo/bar')).method, + equals('GET'), + ); + }); + + test('url is correctly parsed and set in BaseRequest', () { + expect( + Request.uri('GET', Uri.parse('https://foo/bar')).url, + equals(Uri.parse('https://foo/bar')), + ); + + expect( + Request.uri('GET', Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')) + .url, + equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), + ); + + expect( + Request.uri( + 'GET', + Uri.parse('https://foo/bar'), + parameters: { + 'lorem': 'ipsum', + 'dolor': 123, + }, + ).url, + equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), + ); + + expect( + Request.uri( + 'GET', + Uri.parse( + 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b', + ), + parameters: { + 'lorem': 'ipsum', + 'dolor': 123, + 'second_list': ['a', 'b'], + }, + ).url, + equals(Uri.parse( + 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b&lorem=ipsum&dolor=123&second_list=a&second_list=b', + )), + ); + }); + + test('headers are preserved in BaseRequest', () { + final Map headers = { + 'content-type': 'application/json; charset=utf-8', + 'accept': 'application/json; charset=utf-8', + }; + + final Request request = Request.uri( + 'GET', + Uri.parse('https://foo/bar'), + headers: headers, + ); + + expect( + MapEquality().equals(request.headers, headers), + true, + ); + }); + + test('copyWith creates a BaseRequest', () { + expect( + Request.uri('GET', Uri.parse('https://foo/bar')) + .copyWith(method: HttpMethod.Put), + isA(), + ); + }); + }); +} diff --git a/chopper_built_value/test/converter_test.dart b/chopper_built_value/test/converter_test.dart index 2766f645..f47cd763 100644 --- a/chopper_built_value/test/converter_test.dart +++ b/chopper_built_value/test/converter_test.dart @@ -26,7 +26,11 @@ void main() { group('BuiltValueConverter', () { test('convert request', () { - var request = Request('', '', '', body: data); + var request = Request.uri( + HttpMethod.Post, + Uri.parse('https://foo/'), + body: data, + ); request = converter.convertRequest(request); expect(request.body, '{"\$":"DataModel","id":42,"name":"foo"}'); }); @@ -65,7 +69,11 @@ void main() { }); test('has json headers', () { - var request = Request('', '', '', body: data); + var request = Request.uri( + HttpMethod.Get, + Uri.parse('https://foo/'), + body: data, + ); request = converter.convertRequest(request); expect(request.headers['content-type'], equals('application/json')); From bd8d65f5eb97cc61058cc075399bd7c406f8fbe9 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 14 Oct 2022 10:59:00 +0100 Subject: [PATCH 34/61] Exclude null query vars by default and add new @Method annotation includeNullQueryVars (#372) --- chopper/lib/src/annotations.dart | 34 +++ chopper/lib/src/request.dart | 21 +- chopper/lib/src/utils.dart | 24 +- chopper/test/base_test.dart | 116 ++++++++- chopper/test/test_service.chopper.dart | 37 +++ chopper/test/test_service.dart | 15 ++ chopper/test/utils_test.dart | 305 ++++++++++++++++++++++- chopper_generator/analysis_options.yaml | 2 +- chopper_generator/lib/src/generator.dart | 11 + 9 files changed, 548 insertions(+), 17 deletions(-) diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index 5d850deb..5ddda966 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -169,12 +169,39 @@ class Method { /// hxxp://path/to/script?user[name]=john&user[surname]=doe&user[age]=21 final bool useBrackets; + /// Set to [true] to include query variables with null values. This includes nested maps. + /// The default is to exclude them. + /// + /// NOTE: Empty strings are always included. + /// + /// ```dart + /// @Get( + /// path: '/script', + /// includeNullQueryVars: true, + /// ) + /// Future> getData({ + /// @Query('foo') String? foo, + /// @Query('bar') String? bar, + /// @Query('baz') String? baz, + /// }); + /// + /// final response = await service.getData( + /// foo: 'foo_val', + /// bar: null, // omitting it would have the same effect + /// baz: 'baz_val', + /// ); + /// ``` + /// + /// The above code produces hxxp://path/to/script&foo=foo_var&bar=&baz=baz_var + final bool includeNullQueryVars; + const Method( this.method, { this.optionalBody = false, this.path = '', this.headers = const {}, this.useBrackets = false, + this.includeNullQueryVars = false, }); } @@ -186,6 +213,7 @@ class Get extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Get); } @@ -199,6 +227,7 @@ class Post extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Post); } @@ -210,6 +239,7 @@ class Delete extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Delete); } @@ -223,6 +253,7 @@ class Put extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Put); } @@ -235,6 +266,7 @@ class Patch extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Patch); } @@ -246,6 +278,7 @@ class Head extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Head); } @@ -256,6 +289,7 @@ class Options extends Method { super.path, super.headers, super.useBrackets, + super.includeNullQueryVars, }) : super(HttpMethod.Options); } diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index b515ec24..f4189cc9 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -14,6 +14,7 @@ class Request extends http.BaseRequest { final bool multipart; final List parts; final bool useBrackets; + final bool includeNullQueryVars; Request( String method, @@ -25,9 +26,16 @@ class Request extends http.BaseRequest { this.multipart = false, this.parts = const [], this.useBrackets = false, + this.includeNullQueryVars = false, }) : super( method, - buildUri(origin, path, parameters, useBrackets: useBrackets), + buildUri( + origin, + path, + parameters, + useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, + ), ) { this.headers.addAll(headers); } @@ -44,6 +52,7 @@ class Request extends http.BaseRequest { this.multipart = false, this.parts = const [], this.useBrackets = false, + this.includeNullQueryVars = false, }) : origin = url.origin, path = url.path, parameters = {...url.queryParametersAll, ...?parameters}, @@ -54,6 +63,7 @@ class Request extends http.BaseRequest { url.path, {...url.queryParametersAll, ...?parameters}, useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, ), ) { this.headers.addAll(headers); @@ -70,6 +80,7 @@ class Request extends http.BaseRequest { bool? multipart, List? parts, bool? useBrackets, + bool? includeNullQueryVars, }) => Request( method ?? this.method, @@ -81,6 +92,7 @@ class Request extends http.BaseRequest { multipart: multipart ?? this.multipart, parts: parts ?? this.parts, useBrackets: useBrackets ?? this.useBrackets, + includeNullQueryVars: includeNullQueryVars ?? this.includeNullQueryVars, ); /// Builds a valid URI from [baseUrl], [url] and [parameters]. @@ -92,6 +104,7 @@ class Request extends http.BaseRequest { String url, Map parameters, { bool useBrackets = false, + bool includeNullQueryVars = false, }) { // If the request's url is already a fully qualified URL, we can use it // as-is and ignore the baseUrl. @@ -99,7 +112,11 @@ class Request extends http.BaseRequest { ? Uri.parse(url) : Uri.parse('${baseUrl.strip('/')}/${url.leftStrip('/')}'); - final String query = mapToQuery(parameters, useBrackets: useBrackets); + final String query = mapToQuery( + parameters, + useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, + ); return query.isNotEmpty ? uri.replace(query: uri.hasQuery ? '${uri.query}&$query' : query) diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index 85ff8c62..299ddead 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -56,13 +56,22 @@ final chopperLogger = Logger('Chopper'); /// Creates a valid URI query string from [map]. /// /// E.g., `{'foo': 'bar', 'ints': [ 1337, 42 ] }` will become 'foo=bar&ints=1337&ints=42'. -String mapToQuery(Map map, {bool useBrackets = false}) => - _mapToQuery(map, useBrackets: useBrackets).join('&'); +String mapToQuery( + Map map, { + bool useBrackets = false, + bool includeNullQueryVars = false, +}) => + _mapToQuery( + map, + useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, + ).join('&'); Iterable<_Pair> _mapToQuery( Map map, { String? prefix, bool useBrackets = false, + bool includeNullQueryVars = false, }) { final Set<_Pair> pairs = {}; @@ -80,7 +89,12 @@ Iterable<_Pair> _mapToQuery( pairs.addAll(_iterableToQuery(name, value, useBrackets: useBrackets)); } else if (value is Map) { pairs.addAll( - _mapToQuery(value, prefix: name, useBrackets: useBrackets), + _mapToQuery( + value, + prefix: name, + useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, + ), ); } else { pairs.add( @@ -88,7 +102,9 @@ Iterable<_Pair> _mapToQuery( ); } } else { - pairs.add(_Pair(name, '')); + if (includeNullQueryVars) { + pairs.add(_Pair(name, '')); + } } }); diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index ffdd5d9d..42b2a39c 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -110,7 +110,7 @@ void main() { final httpClient = MockClient((request) async { expect( request.url.toString(), - equals('$baseUrl/test/query?name=&int=&default_value='), + equals('$baseUrl/test/query?name='), ); expect(request.method, equals('GET')); @@ -132,7 +132,7 @@ void main() { final httpClient = MockClient((request) async { expect( request.url.toString(), - equals('$baseUrl/test/query?name=&int=&default_value=42'), + equals('$baseUrl/test/query?name=&default_value=42'), ); expect(request.method, equals('GET')); @@ -923,6 +923,34 @@ void main() { ); }); + test('Include null query vars', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/query_param_include_null_query_vars' + '?foo=foo_val' + '&bar=' + '&baz=baz_val'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getUsingQueryParamIncludeNullQueryVars( + foo: 'foo_val', + baz: 'baz_val', + ); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('List query param', () async { final httpClient = MockClient((request) async { expect( @@ -1067,4 +1095,88 @@ void main() { httpClient.close(); }); + + test('Map query param without including null query vars', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/map_query_param' + '?value.bar=baz' + '&value.etc.abc=def' + '&value.etc.mno.opq=rst' + '&value.etc.mno.list=a' + '&value.etc.mno.list=123' + '&value.etc.mno.list=false'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getUsingMapQueryParam({ + 'bar': 'baz', + 'zap': null, + 'etc': { + 'abc': 'def', + 'ghi': null, + 'mno': { + 'opq': 'rst', + 'uvw': null, + 'list': ['a', 123, false], + }, + }, + }); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('Map query param including null query vars', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/map_query_param_include_null_query_vars' + '?value.bar=baz' + '&value.zap=' + '&value.etc.abc=def' + '&value.etc.ghi=' + '&value.etc.mno.opq=rst' + '&value.etc.mno.uvw=' + '&value.etc.mno.list=a' + '&value.etc.mno.list=123' + '&value.etc.mno.list=false'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service + .getUsingMapQueryParamIncludeNullQueryVars({ + 'bar': 'baz', + 'zap': null, + 'etc': { + 'abc': 'def', + 'ghi': null, + 'mno': { + 'opq': 'rst', + 'uvw': null, + 'list': ['a', 123, false], + }, + }, + }); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index d0a43b5c..fce9c168 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -489,6 +489,28 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> getUsingQueryParamIncludeNullQueryVars({ + String? foo, + String? bar, + String? baz, + }) { + final String $url = '/test/query_param_include_null_query_vars'; + final Map $params = { + 'foo': foo, + 'bar': bar, + 'baz': baz, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + includeNullQueryVars: true, + ); + return client.send($request); + } + @override Future> getUsingListQueryParam(List value) { final String $url = '/test/list_query_param'; @@ -530,6 +552,21 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> getUsingMapQueryParamIncludeNullQueryVars( + Map value) { + final String $url = '/test/map_query_param_include_null_query_vars'; + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + includeNullQueryVars: true, + ); + return client.send($request); + } + @override Future> getUsingMapQueryParamWithBrackets( Map value) { diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 7789b361..7d19418c 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -139,6 +139,13 @@ abstract class HttpTestService extends ChopperService { @Post(path: 'no-body') Future noBody(); + @Get(path: '/query_param_include_null_query_vars', includeNullQueryVars: true) + Future> getUsingQueryParamIncludeNullQueryVars({ + @Query('foo') String? foo, + @Query('bar') String? bar, + @Query('baz') String? baz, + }); + @Get(path: '/list_query_param') Future> getUsingListQueryParam( @Query('value') List value, @@ -154,6 +161,14 @@ abstract class HttpTestService extends ChopperService { @Query('value') Map value, ); + @Get( + path: '/map_query_param_include_null_query_vars', + includeNullQueryVars: true, + ) + Future> getUsingMapQueryParamIncludeNullQueryVars( + @Query('value') Map value, + ); + @Get(path: '/map_query_param_with_brackets', useBrackets: true) Future> getUsingMapQueryParamWithBrackets( @Query('value') Map value, diff --git a/chopper/test/utils_test.dart b/chopper/test/utils_test.dart index 649a4651..bf3f8e6f 100644 --- a/chopper/test/utils_test.dart +++ b/chopper/test/utils_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('mapToQuery single', () { , String>{ - {'foo': null}: 'foo=', + {'foo': null}: '', {'foo': ''}: 'foo=', {'foo': ' '}: 'foo=%20', {'foo': ' '}: 'foo=%20%20', @@ -28,7 +28,62 @@ void main() { test('$map -> $query', () => expect(mapToQuery(map), query))); }); + group('mapToQuery single with includeNullQueryVars', () { + , String>{ + {'foo': null}: 'foo=', + {'foo': ''}: 'foo=', + {'foo': ' '}: 'foo=%20', + {'foo': ' '}: 'foo=%20%20', + {'foo': '\t'}: 'foo=%09', + {'foo': '\t\t'}: 'foo=%09%09', + {'foo': 'null'}: 'foo=null', + {'foo': 'bar'}: 'foo=bar', + {'foo': ' bar '}: 'foo=%20bar%20', + {'foo': '\tbar\t'}: 'foo=%09bar%09', + {'foo': '\t\tbar\t\t'}: 'foo=%09%09bar%09%09', + {'foo': 123}: 'foo=123', + {'foo': 0}: 'foo=0', + {'foo': -0.01}: 'foo=-0.01', + {'foo': '0.00'}: 'foo=0.00', + {'foo': 123.456}: 'foo=123.456', + {'foo': 123.450}: 'foo=123.45', + {'foo': -123.456}: 'foo=-123.456', + {'foo': true}: 'foo=true', + {'foo': false}: 'foo=false', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect(mapToQuery(map, includeNullQueryVars: true), query), + ), + ); + }); + group('mapToQuery multiple', () { + , String>{ + {'foo': null, 'baz': null}: '', + {'foo': '', 'baz': ''}: 'foo=&baz=', + {'foo': null, 'baz': ''}: 'baz=', + {'foo': '', 'baz': null}: 'foo=', + {'foo': 'bar', 'baz': ''}: 'foo=bar&baz=', + {'foo': null, 'baz': 'etc'}: 'baz=etc', + {'foo': '', 'baz': 'etc'}: 'foo=&baz=etc', + {'foo': 'bar', 'baz': 'etc'}: 'foo=bar&baz=etc', + {'foo': 'null', 'baz': 'null'}: 'foo=null&baz=null', + {'foo': ' ', 'baz': ' '}: 'foo=%20&baz=%20', + {'foo': '\t', 'baz': '\t'}: 'foo=%09&baz=%09', + {'foo': 123, 'baz': 456}: 'foo=123&baz=456', + {'foo': 0, 'baz': 0}: 'foo=0&baz=0', + {'foo': '0.00', 'baz': '0.00'}: 'foo=0.00&baz=0.00', + {'foo': 123.456, 'baz': 789.012}: 'foo=123.456&baz=789.012', + {'foo': 123.450, 'baz': 789.010}: 'foo=123.45&baz=789.01', + {'foo': -123.456, 'baz': -789.012}: 'foo=-123.456&baz=-789.012', + {'foo': true, 'baz': true}: 'foo=true&baz=true', + {'foo': false, 'baz': false}: 'foo=false&baz=false', + }.forEach((map, query) => + test('$map -> $query', () => expect(mapToQuery(map), query))); + }); + + group('mapToQuery multiple with includeNullQueryVars', () { , String>{ {'foo': null, 'baz': null}: 'foo=&baz=', {'foo': '', 'baz': ''}: 'foo=&baz=', @@ -49,8 +104,12 @@ void main() { {'foo': -123.456, 'baz': -789.012}: 'foo=-123.456&baz=-789.012', {'foo': true, 'baz': true}: 'foo=true&baz=true', {'foo': false, 'baz': false}: 'foo=false&baz=false', - }.forEach((map, query) => - test('$map -> $query', () => expect(mapToQuery(map), query))); + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect(mapToQuery(map, includeNullQueryVars: true), query), + ), + ); }); group('mapToQuery lists', () { @@ -90,11 +149,57 @@ void main() { 'bar': 'baz', 'etc': '', 'xyz': null, - }: 'foo=bar&foo=baz&foo=etc&bar=baz&etc=&xyz=', + }: 'foo=bar&foo=baz&foo=etc&bar=baz&etc=', }.forEach((map, query) => test('$map -> $query', () => expect(mapToQuery(map), query))); }); + group('mapToQuery lists with includeNullQueryVars', () { + , String>{ + { + 'foo': ['bar', 'baz', 'etc'], + }: 'foo=bar&foo=baz&foo=etc', + { + 'foo': ['bar', 123, 456.789, 0, -123, -456.789], + }: 'foo=bar&foo=123&foo=456.789&foo=0&foo=-123&foo=-456.789', + { + 'foo': ['', 'baz', 'etc'], + }: 'foo=baz&foo=etc', + { + 'foo': ['bar', '', 'etc'], + }: 'foo=bar&foo=etc', + { + 'foo': ['bar', 'baz', ''], + }: 'foo=bar&foo=baz', + { + 'foo': [null, 'baz', 'etc'], + }: 'foo=baz&foo=etc', + { + 'foo': ['bar', null, 'etc'], + }: 'foo=bar&foo=etc', + { + 'foo': ['bar', 'baz', null], + }: 'foo=bar&foo=baz', + { + 'foo': ['bar', 'baz', ' '], + }: 'foo=bar&foo=baz&foo=%20', + { + 'foo': ['bar', 'baz', '\t'], + }: 'foo=bar&foo=baz&foo=%09', + { + 'foo': ['bar', 'baz', 'etc'], + 'bar': 'baz', + 'etc': '', + 'xyz': null, + }: 'foo=bar&foo=baz&foo=etc&bar=baz&etc=&xyz=', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect(mapToQuery(map, includeNullQueryVars: true), query), + ), + ); + }); + group('mapToQuery lists with brackets', () { , String>{ { @@ -132,7 +237,7 @@ void main() { 'bar': 'baz', 'etc': '', 'xyz': null, - }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc&bar=baz&etc=&xyz=', + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc&bar=baz&etc=', }.forEach( (map, query) => test( '$map -> $query', @@ -144,6 +249,55 @@ void main() { ); }); + group('mapToQuery lists with brackets with includeNullQueryVars', () { + , String>{ + { + 'foo': ['bar', 'baz', 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', 123, 456.789, 0, -123, -456.789], + }: 'foo%5B%5D=bar&foo%5B%5D=123&foo%5B%5D=456.789&foo%5B%5D=0&foo%5B%5D=-123&foo%5B%5D=-456.789', + { + 'foo': ['', 'baz', 'etc'], + }: 'foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', '', 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=etc', + { + 'foo': ['bar', 'baz', ''], + }: 'foo%5B%5D=bar&foo%5B%5D=baz', + { + 'foo': [null, 'baz', 'etc'], + }: 'foo%5B%5D=baz&foo%5B%5D=etc', + { + 'foo': ['bar', null, 'etc'], + }: 'foo%5B%5D=bar&foo%5B%5D=etc', + { + 'foo': ['bar', 'baz', null], + }: 'foo%5B%5D=bar&foo%5B%5D=baz', + { + 'foo': ['bar', 'baz', ' '], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=%20', + { + 'foo': ['bar', 'baz', '\t'], + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=%09', + { + 'foo': ['bar', 'baz', 'etc'], + 'bar': 'baz', + 'etc': '', + 'xyz': null, + }: 'foo%5B%5D=bar&foo%5B%5D=baz&foo%5B%5D=etc&bar=baz&etc=&xyz=', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect( + mapToQuery(map, useBrackets: true, includeNullQueryVars: true), + query, + ), + ), + ); + }); + group('mapToQuery maps', () { , String>{ { @@ -154,7 +308,7 @@ void main() { }: 'foo.bar=', { 'foo': {'bar': null}, - }: 'foo.bar=', + }: '', { 'foo': {'bar': ' '}, }: 'foo.bar=%20', @@ -178,7 +332,7 @@ void main() { 'tab': '\t', 'list': ['a', 123, false], }, - }: 'foo.bar=baz&foo.int=123&foo.double=456.789&foo.zero=0&foo.negInt=-123&foo.negDouble=-456.789&foo.emptyString=&foo.nullValue=&foo.space=%20&foo.tab=%09&foo.list=a&foo.list=123&foo.list=false', + }: 'foo.bar=baz&foo.int=123&foo.double=456.789&foo.zero=0&foo.negInt=-123&foo.negDouble=-456.789&foo.emptyString=&foo.space=%20&foo.tab=%09&foo.list=a&foo.list=123&foo.list=false', { 'foo': {'bar': 'baz'}, 'etc': 'xyz', @@ -206,7 +360,142 @@ void main() { test('$map -> $query', () => expect(mapToQuery(map), query))); }); + group('mapToQuery maps with includeNullQueryVars', () { + , String>{ + { + 'foo': {'bar': 'baz'}, + }: 'foo.bar=baz', + { + 'foo': {'bar': ''}, + }: 'foo.bar=', + { + 'foo': {'bar': null}, + }: 'foo.bar=', + { + 'foo': {'bar': ' '}, + }: 'foo.bar=%20', + { + 'foo': {'bar': '\t'}, + }: 'foo.bar=%09', + { + 'foo': {'bar': 'baz', 'etc': 'xyz', 'space': ' ', 'tab': '\t'}, + }: 'foo.bar=baz&foo.etc=xyz&foo.space=%20&foo.tab=%09', + { + 'foo': { + 'bar': 'baz', + 'int': 123, + 'double': 456.789, + 'zero': 0, + 'negInt': -123, + 'negDouble': -456.789, + 'emptyString': '', + 'nullValue': null, + 'space': ' ', + 'tab': '\t', + 'list': ['a', 123, false], + }, + }: 'foo.bar=baz&foo.int=123&foo.double=456.789&foo.zero=0&foo.negInt=-123&foo.negDouble=-456.789&foo.emptyString=&foo.nullValue=&foo.space=%20&foo.tab=%09&foo.list=a&foo.list=123&foo.list=false', + { + 'foo': {'bar': 'baz'}, + 'etc': 'xyz', + }: 'foo.bar=baz&etc=xyz', + { + 'foo': { + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'aab': [ + 'bbc', + 'ccd', + 'eef', + ], + }, + }, + }, + }: 'foo.bar=baz&foo.zap=abc&foo.etc.abc=def&foo.etc.ghi=jkl&foo.etc.mno.opq=rst&foo.etc.mno.uvw=xyz&foo.etc.mno.aab=bbc&foo.etc.mno.aab=ccd&foo.etc.mno.aab=eef', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect(mapToQuery(map, includeNullQueryVars: true), query), + ), + ); + }); + group('mapToQuery maps with brackets', () { + , String>{ + { + 'foo': {'bar': 'baz'}, + }: 'foo%5Bbar%5D=baz', + { + 'foo': {'bar': ''}, + }: 'foo%5Bbar%5D=', + { + 'foo': {'bar': null}, + }: '', + { + 'foo': {'bar': ' '}, + }: 'foo%5Bbar%5D=%20', + { + 'foo': {'bar': '\t'}, + }: 'foo%5Bbar%5D=%09', + { + 'foo': {'bar': 'baz', 'etc': 'xyz', 'space': ' ', 'tab': '\t'}, + }: 'foo%5Bbar%5D=baz&foo%5Betc%5D=xyz&foo%5Bspace%5D=%20&foo%5Btab%5D=%09', + { + 'foo': { + 'bar': 'baz', + 'int': 123, + 'double': 456.789, + 'zero': 0, + 'negInt': -123, + 'negDouble': -456.789, + 'emptyString': '', + 'nullValue': null, + 'space': ' ', + 'tab': '\t', + 'list': ['a', 123, false], + }, + }: 'foo%5Bbar%5D=baz&foo%5Bint%5D=123&foo%5Bdouble%5D=456.789&foo%5Bzero%5D=0&foo%5BnegInt%5D=-123&foo%5BnegDouble%5D=-456.789&foo%5BemptyString%5D=&foo%5Bspace%5D=%20&foo%5Btab%5D=%09&foo%5Blist%5D%5B%5D=a&foo%5Blist%5D%5B%5D=123&foo%5Blist%5D%5B%5D=false', + { + 'foo': {'bar': 'baz'}, + 'etc': 'xyz', + }: 'foo%5Bbar%5D=baz&etc=xyz', + { + 'foo': { + 'bar': 'baz', + 'zap': 'abc', + 'etc': { + 'abc': 'def', + 'ghi': 'jkl', + 'mno': { + 'opq': 'rst', + 'uvw': 'xyz', + 'aab': [ + 'bbc', + 'ccd', + 'eef', + ], + }, + }, + }, + }: 'foo%5Bbar%5D=baz&foo%5Bzap%5D=abc&foo%5Betc%5D%5Babc%5D=def&foo%5Betc%5D%5Bghi%5D=jkl&foo%5Betc%5D%5Bmno%5D%5Bopq%5D=rst&foo%5Betc%5D%5Bmno%5D%5Buvw%5D=xyz&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=bbc&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=ccd&foo%5Betc%5D%5Bmno%5D%5Baab%5D%5B%5D=eef', + }.forEach( + (map, query) => test( + '$map -> $query', + () => expect( + mapToQuery(map, useBrackets: true), + query, + ), + ), + ); + }); + + group('mapToQuery maps with brackets with includeNullQueryVars', () { , String>{ { 'foo': {'bar': 'baz'}, @@ -268,7 +557,7 @@ void main() { (map, query) => test( '$map -> $query', () => expect( - mapToQuery(map, useBrackets: true), + mapToQuery(map, useBrackets: true, includeNullQueryVars: true), query, ), ), diff --git a/chopper_generator/analysis_options.yaml b/chopper_generator/analysis_options.yaml index 3a82dc3b..2caa0f09 100644 --- a/chopper_generator/analysis_options.yaml +++ b/chopper_generator/analysis_options.yaml @@ -14,7 +14,7 @@ dart_code_metrics: cyclomatic-complexity: 20 number-of-arguments: 4 maximum-nesting-level: 5 - number-of-parameters: 6 + number-of-parameters: 10 source-lines-of-code: 250 metrics-exclude: - test/** diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 6dda3c4a..2ccd6cd3 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -311,6 +311,8 @@ class ChopperGenerator extends GeneratorForAnnotation { final bool useBrackets = getUseBrackets(method); + final bool includeNullQueryVars = getIncludeNullQueryVars(method); + blocks.add( declareFinal(_requestVar, type: refer('Request')) .assign( @@ -321,6 +323,7 @@ class ChopperGenerator extends GeneratorForAnnotation { useHeaders: headers != null, hasParts: hasParts, useBrackets: useBrackets, + includeNullQueryVars: includeNullQueryVars, ), ) .statement, @@ -494,6 +497,7 @@ class ChopperGenerator extends GeneratorForAnnotation { bool useQueries = false, bool useHeaders = false, bool useBrackets = false, + bool includeNullQueryVars = false, }) { final List params = [ literal(getMethodName(method)), @@ -524,6 +528,10 @@ class ChopperGenerator extends GeneratorForAnnotation { namedParams['useBrackets'] = literalBool(useBrackets); } + if (includeNullQueryVars) { + namedParams['includeNullQueryVars'] = literalBool(includeNullQueryVars); + } + return refer('Request').newInstance(params, namedParams); } @@ -631,6 +639,9 @@ String getMethodName(ConstantReader method) => bool getUseBrackets(ConstantReader method) => method.peek('useBrackets')?.boolValue ?? false; +bool getIncludeNullQueryVars(ConstantReader method) => + method.peek('includeNullQueryVars')?.boolValue ?? false; + extension DartTypeExtension on DartType { bool get isNullable => nullabilitySuffix != NullabilitySuffix.none; } From 786c1535b1b85e1f55f4451c0284cd66e64d7dbe Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Sat, 15 Oct 2022 08:30:21 +0300 Subject: [PATCH 35/61] 5.1.0 (dev) (#373) Co-authored-by: Ivan Terekhin <231950+JEuler@users.noreply.github.com> --- chopper/CHANGELOG.md | 5 +++++ chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index e3d9a080..56519d03 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 5.1.0 + +- Base class changed for http.BaseRequest +- Annotation to include null vars in query + ## 5.0.1 - mapToQuery changes diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 68122b13..51d7abcd 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.0.1 +version: 5.1.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 67f4b0e1..185ca6c8 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 5.1.0 + +- Annotation to include null vars in query + ## 5.0.1 - Types added diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 2fedb49a..6937cc90 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.0.1 +version: 5.1.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 9b2b867f441b5952561dfcdce2c01aa1cb42ba35 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 15 Oct 2022 17:22:20 +0100 Subject: [PATCH 36/61] [workflow] Upgrade mono_repo to v6.4.1 (#375) --- .github/workflows/dart.yml | 85 ++++++++++++++++++++--------------- .github/workflows/publish.yml | 12 ++--- mono_repo.yaml | 4 +- tool/ci.sh | 2 +- 4 files changed, 57 insertions(+), 46 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 9bf38a8f..df934002 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v6.0.0 +# Created with package:mono_repo v6.4.1 name: Dart CI on: push: @@ -14,6 +14,7 @@ defaults: shell: bash env: PUB_ENVIRONMENT: bot.github +permissions: read-all jobs: job_001: @@ -21,20 +22,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2.1.7 + uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable" restore-keys: | os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.3 + - name: Setup Dart SDK + uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: stable - id: checkout - uses: actions/checkout@v2.4.0 + name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - name: mono_repo self validate - run: dart pub global activate mono_repo 6.0.0 + run: dart pub global activate mono_repo 6.4.1 - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: @@ -42,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2.1.7 + uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper_built_value-chopper_generator;commands:format-analyze" @@ -51,43 +54,45 @@ jobs: os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.3 + - name: Setup Dart SDK + uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: stable - id: checkout - uses: actions/checkout@v2.4.0 + name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: dart pub upgrade - name: "chopper_built_value; dart format --output=none --set-exit-if-changed ." + run: "dart format --output=none --set-exit-if-changed ." if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: "dart format --output=none --set-exit-if-changed ." - name: "chopper_built_value; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: dart analyze --fatal-infos . - id: chopper_generator_pub_upgrade name: chopper_generator; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_generator - run: dart pub upgrade - name: "chopper_generator; dart format --output=none --set-exit-if-changed ." + run: "dart format --output=none --set-exit-if-changed ." if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - run: "dart format --output=none --set-exit-if-changed ." - name: "chopper_generator; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - run: dart analyze --fatal-infos . job_003: name: "analyze_and_format; PKG: chopper; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2.1.7 + uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" @@ -96,24 +101,26 @@ jobs: os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.3 + - name: Setup Dart SDK + uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: stable - id: checkout - uses: actions/checkout@v2.4.0 + name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - id: chopper_pub_upgrade name: chopper; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - run: dart pub upgrade - name: "chopper; dart format --output=none --set-exit-if-changed ." + run: "dart format --output=none --set-exit-if-changed ." if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: "dart format --output=none --set-exit-if-changed ." - name: "chopper; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: dart analyze --fatal-infos . needs: - job_001 - job_002 @@ -122,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2.1.7 + uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_1" @@ -131,29 +138,31 @@ jobs: os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.3 + - name: Setup Dart SDK + uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: stable - id: checkout - uses: actions/checkout@v2.4.0 + name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - id: chopper_pub_upgrade name: chopper; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - run: dart pub upgrade - name: "chopper; dart test -p chrome" + run: dart test -p chrome if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: dart test -p chrome - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: dart pub upgrade - name: "chopper_built_value; dart test -p chrome" + run: dart test -p chrome if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: dart test -p chrome needs: - job_001 - job_002 @@ -163,7 +172,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@v2.1.7 + uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_0" @@ -172,29 +181,31 @@ jobs: os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - - uses: dart-lang/setup-dart@v1.3 + - name: Setup Dart SDK + uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d with: sdk: stable - id: checkout - uses: actions/checkout@v2.4.0 + name: Checkout repository + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - id: chopper_pub_upgrade name: chopper; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - run: dart pub upgrade - name: chopper; dart test + run: dart test if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - run: dart test - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade + run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - run: dart pub upgrade - name: chopper_built_value; dart test + run: dart test if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value - run: dart test needs: - job_001 - job_002 @@ -203,15 +214,15 @@ jobs: name: Coverage runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - id: upload_coverage name: chopper; tool/coverage.sh - if: "always() && steps.checkout.conclusion == 'success'" run: bash tool/coverage.sh + if: "always() && steps.checkout.conclusion == 'success'" env: CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" needs: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b2ac60ec..aea02d47 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,11 +9,11 @@ jobs: name: "Publish chopper" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - id: publish run: bash tool/publish.sh chopper env: @@ -22,11 +22,11 @@ jobs: name: "Publish chopper_generator" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - id: publish run: bash tool/publish.sh chopper_generator env: @@ -35,11 +35,11 @@ jobs: name: "Publish chopper_built_value" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - id: publish run: bash tool/publish.sh chopper_built_value env: diff --git a/mono_repo.yaml b/mono_repo.yaml index a7fafb4c..08251824 100644 --- a/mono_repo.yaml +++ b/mono_repo.yaml @@ -14,11 +14,11 @@ github: - name: "Coverage" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.0 + - uses: dart-lang/setup-dart@v1.3 with: sdk: stable - id: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - id: upload_coverage name: "chopper; tool/coverage.sh" if: "always() && steps.checkout.conclusion == 'success'" diff --git a/tool/ci.sh b/tool/ci.sh index d614ed80..5017b716 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Created with package:mono_repo v6.0.0 +# Created with package:mono_repo v6.4.1 # Support built in commands on windows out of the box. # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") From 233678e90a6d07847cdbfa18337ae8b6adc8c29a Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 17 Oct 2022 07:38:11 +0100 Subject: [PATCH 37/61] Update analyzer to >=4.4.0 <6.0.0 (#378) --- chopper_generator/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 6937cc90..a0fe86dc 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: ">=2.17.0 <3.0.0" dependencies: - analyzer: ^4.4.0 + analyzer: '>=4.4.0 <6.0.0' build: ^2.0.0 built_collection: ^5.0.0 chopper: ^5.0.0 From 1b2f1fa91369f13b4e8be0438ce9b93095f9a7d3 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Tue, 18 Oct 2022 06:00:49 +0100 Subject: [PATCH 38/61] Update mono_repo to 6.4.2 (#380) --- .github/workflows/dart.yml | 24 ++++++++++++------------ tool/ci.sh | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index df934002..25ae548e 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v6.4.1 +# Created with package:mono_repo v6.4.2 name: Dart CI on: push: @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable" @@ -35,9 +35,9 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - name: mono_repo self validate - run: dart pub global activate mono_repo 6.4.1 + run: dart pub global activate mono_repo 6.4.2 - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper_built_value-chopper_generator;commands:format-analyze" @@ -60,7 +60,7 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade run: dart pub upgrade @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" @@ -107,7 +107,7 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - id: chopper_pub_upgrade name: chopper; dart pub upgrade run: dart pub upgrade @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_1" @@ -144,7 +144,7 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - id: chopper_pub_upgrade name: chopper; dart pub upgrade run: dart pub upgrade @@ -172,7 +172,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@ac8075791e805656e71b4ba23325ace9e3421120 + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_0" @@ -187,7 +187,7 @@ jobs: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - id: chopper_pub_upgrade name: chopper; dart pub upgrade run: dart pub upgrade diff --git a/tool/ci.sh b/tool/ci.sh index 5017b716..372d5024 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Created with package:mono_repo v6.4.1 +# Created with package:mono_repo v6.4.2 # Support built in commands on windows out of the box. # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") From 1e3e7f4c6917e356590f18d278f990495c5987d4 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Tue, 18 Oct 2022 10:18:29 +0300 Subject: [PATCH 39/61] add techouse (#382) Co-authored-by: Ivan Terekhin <231950+JEuler@users.noreply.github.com> --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a0b3f6a5..a6a0d409 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Hadrien Lejard

💻 👀 ⚠️ 📖
István Juhos

💻 👀 ⚠️ 📖 +
Klemen Tusar

💻 👀 ⚠️ 📖
Ivan Terekhin

💻 👀 ⚠️ 📖
Eugeny Sampir

💻
Uladzimir_Paliukhovich

💻 @@ -50,4 +51,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! From b0fd18f7e1fb46968dc4a47f3dd61b69aaaaa92e Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Mon, 31 Oct 2022 07:06:22 +0100 Subject: [PATCH 40/61] [Feature] Replace the String based path with Uri (#383) --- chopper/example/definition.chopper.dart | 12 +- chopper/example/main.dart | 2 +- chopper/lib/src/base.dart | 50 +++--- chopper/lib/src/request.dart | 92 +++++----- chopper/test/base_test.dart | 168 +++++++++++++++--- chopper/test/client_test.dart | 38 ++-- chopper/test/converter_test.dart | 9 +- chopper/test/interceptors_test.dart | 14 +- chopper/test/multipart_test.dart | 15 +- chopper/test/request_test.dart | 145 ++++++++++++--- chopper/test/test_service.chopper.dart | 72 ++++---- chopper_built_value/test/converter_test.dart | 6 +- chopper_generator/lib/src/generator.dart | 19 +- example/bin/main_built_value.dart | 2 +- example/bin/main_json_serializable.dart | 2 +- ...son_serializable_squadron_worker_pool.dart | 2 +- example/lib/built_value_resource.chopper.dart | 8 +- example/lib/json_serializable.chopper.dart | 10 +- faq.md | 8 +- 19 files changed, 467 insertions(+), 207 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index 884b3470..c7c86ef7 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}'; + final Uri $url = Uri.parse('/resources/${id}'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $params = {'id': id}; final Map $headers = { 'foo': 'bar', @@ -46,7 +46,7 @@ class _$MyService extends MyService { @override Future>>> getListResources() { - final String $url = '/resources/resources'; + final Uri $url = Uri.parse('/resources/resources'); final Request $request = Request( 'GET', $url, @@ -61,7 +61,7 @@ class _$MyService extends MyService { String toto, String b, ) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final $body = { 'a': toto, 'b': b, @@ -81,7 +81,7 @@ class _$MyService extends MyService { Map b, String c, ) { - final String $url = '/resources/multi'; + final Uri $url = Uri.parse('/resources/multi'); final List $parts = [ PartValue>( '1', @@ -108,7 +108,7 @@ class _$MyService extends MyService { @override Future> postFile(List bytes) { - final String $url = '/resources/file'; + final Uri $url = Uri.parse('/resources/file'); final List $parts = [ PartValue>( 'file', diff --git a/chopper/example/main.dart b/chopper/example/main.dart index 16f66abc..b7a2cad4 100644 --- a/chopper/example/main.dart +++ b/chopper/example/main.dart @@ -4,7 +4,7 @@ import 'definition.dart'; Future main() async { final chopper = ChopperClient( - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), services: [ // the generated service MyService.create(ChopperClient()), diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 4e594584..624607b3 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -29,7 +29,7 @@ final List allowedInterceptorsType = [ class ChopperClient { /// Base URL of each request of the registered services. /// E.g., the hostname of your service. - final String baseUrl; + final Uri baseUrl; /// The [http.Client] used to make network calls. final http.Client httpClient; @@ -60,6 +60,7 @@ class ChopperClient { /// The base URL of each request of the registered services can be defined /// with the [baseUrl] parameter. /// E.g., the hostname of your service. + /// If not provided, a empty default [Uri] will be used. /// /// A custom HTTP client can be passed as the [client] parameter to be used /// with the created [ChopperClient]. @@ -70,7 +71,7 @@ class ChopperClient { /// /// ```dart /// final chopper = ChopperClient( - /// baseUrl: 'localhost:8000', + /// baseUrl: Uri.parse('localhost:8000'), /// services: [ /// // Add a generated service /// TodosListService.create() @@ -111,14 +112,19 @@ class ChopperClient { /// ); /// ``` ChopperClient({ - this.baseUrl = '', + Uri? baseUrl, http.Client? client, Iterable interceptors = const [], this.authenticator, this.converter, this.errorConverter, Iterable services = const [], - }) : httpClient = client ?? http.Client(), + }) : assert( + baseUrl == null || !baseUrl.hasQuery, + 'baseUrl should not contain query parameters.' + 'Use a request interceptor to add default query parameters'), + baseUrl = baseUrl ?? Uri(), + httpClient = client ?? http.Client(), _clientIsInternal = client == null { if (!interceptors.every(_isAnInterceptor)) { throw ArgumentError( @@ -152,7 +158,7 @@ class ChopperClient { /// /// ```dart /// final chopper = ChopperClient( - /// baseUrl: 'localhost:8000', + /// baseUrl: Uri.parse('localhost:8000'), /// services: [ /// // Add a generated service /// TodosListService.create() @@ -341,10 +347,10 @@ class ChopperClient { /// Makes a HTTP GET request using the [send] function. Future> get( - String url, { + Uri url, { Map headers = const {}, + Uri? baseUrl, Map parameters = const {}, - String? baseUrl, dynamic body, }) => send( @@ -360,13 +366,13 @@ class ChopperClient { /// Makes a HTTP POST request using the [send] function Future> post( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -376,20 +382,20 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP PUT request using the [send] function. Future> put( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -399,20 +405,20 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP PATCH request using the [send] function. Future> patch( - String url, { + Uri url, { dynamic body, List parts = const [], Map headers = const {}, Map parameters = const {}, bool multipart = false, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -422,17 +428,17 @@ class ChopperClient { body: body, parts: parts, headers: headers, - multipart: multipart, parameters: parameters, + multipart: multipart, ), ); /// Makes a HTTP DELETE request using the [send] function. Future> delete( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -446,10 +452,10 @@ class ChopperClient { /// Makes a HTTP HEAD request using the [send] function. Future> head( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( @@ -463,10 +469,10 @@ class ChopperClient { /// Makes a HTTP OPTIONS request using the [send] function. Future> options( - String url, { + Uri url, { Map headers = const {}, Map parameters = const {}, - String? baseUrl, + Uri? baseUrl, }) => send( Request( diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index f4189cc9..e418f2c2 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -7,8 +7,8 @@ import 'package:meta/meta.dart'; /// This class represents an HTTP request that can be made with Chopper. class Request extends http.BaseRequest { - final String path; - final String origin; + final Uri uri; + final Uri baseUri; final dynamic body; final Map parameters; final bool multipart; @@ -18,34 +18,8 @@ class Request extends http.BaseRequest { Request( String method, - this.path, - this.origin, { - this.body, - this.parameters = const {}, - Map headers = const {}, - this.multipart = false, - this.parts = const [], - this.useBrackets = false, - this.includeNullQueryVars = false, - }) : super( - method, - buildUri( - origin, - path, - parameters, - useBrackets: useBrackets, - includeNullQueryVars: includeNullQueryVars, - ), - ) { - this.headers.addAll(headers); - } - - /// Build the Chopper [Request] using a [Uri] instead of a [path] and [origin]. - /// Both the query parameters in the [Uri] and those provided explicitly in - /// the [parameters] are merged together. - Request.uri( - String method, - Uri url, { + this.uri, + this.baseUri, { this.body, Map? parameters, Map headers = const {}, @@ -53,15 +27,18 @@ class Request extends http.BaseRequest { this.parts = const [], this.useBrackets = false, this.includeNullQueryVars = false, - }) : origin = url.origin, - path = url.path, - parameters = {...url.queryParametersAll, ...?parameters}, + }) : assert( + !baseUri.hasQuery, + 'baseUri should not contain query parameters.' + 'Use a request interceptor to add default query parameters'), + // Merge uri.queryParametersAll in the final parameters object so the request object reflects all configured queryParameters + parameters = {...uri.queryParametersAll, ...?parameters}, super( method, buildUri( - url.origin, - url.path, - {...url.queryParametersAll, ...?parameters}, + baseUri, + uri, + {...uri.queryParametersAll, ...?parameters}, useBrackets: useBrackets, includeNullQueryVars: includeNullQueryVars, ), @@ -72,8 +49,8 @@ class Request extends http.BaseRequest { /// Makes a copy of this [Request], replacing original values with the given ones. Request copyWith({ String? method, - String? path, - String? origin, + Uri? uri, + Uri? baseUri, dynamic body, Map? parameters, Map? headers, @@ -84,8 +61,8 @@ class Request extends http.BaseRequest { }) => Request( method ?? this.method, - path ?? this.path, - origin ?? this.origin, + uri ?? this.uri, + baseUri ?? this.baseUri, body: body ?? this.body, parameters: parameters ?? this.parameters, headers: headers ?? this.headers, @@ -100,27 +77,44 @@ class Request extends http.BaseRequest { /// If [url] starts with 'http://' or 'https://', baseUrl is ignored. @visibleForTesting static Uri buildUri( - String baseUrl, - String url, + Uri baseUrl, + Uri url, Map parameters, { bool useBrackets = false, bool includeNullQueryVars = false, }) { // If the request's url is already a fully qualified URL, we can use it // as-is and ignore the baseUrl. - final Uri uri = url.startsWith('http://') || url.startsWith('https://') - ? Uri.parse(url) - : Uri.parse('${baseUrl.strip('/')}/${url.leftStrip('/')}'); + final Uri uri = url.isScheme('HTTP') || url.isScheme('HTTPS') + ? url + : _mergeUri(baseUrl, url); + + // Check if parameter also has all the queryParameters from the url (not the merged uri) + final bool parametersContainsUriQuery = parameters.keys + .every((element) => url.queryParametersAll.keys.contains(element)); + final Map allParameters = parametersContainsUriQuery + ? parameters + : {...url.queryParametersAll, ...parameters}; final String query = mapToQuery( - parameters, + allParameters, useBrackets: useBrackets, includeNullQueryVars: includeNullQueryVars, ); - return query.isNotEmpty - ? uri.replace(query: uri.hasQuery ? '${uri.query}&$query' : query) - : uri; + return query.isNotEmpty ? uri.replace(query: query) : uri; + } + + /// Merges Uri into another Uri preserving queries and paths + static Uri _mergeUri(Uri baseUri, Uri addToUri) { + final path = baseUri.hasEmptyPath + ? addToUri.path + : '${baseUri.path.rightStrip('/')}/${addToUri.path.leftStrip('/')}'; + + return baseUri.replace( + path: path, + query: addToUri.hasQuery ? addToUri.query : null, + ); } /// Converts this Chopper Request into a [http.BaseRequest]. diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 42b2a39c..d6486c2e 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -10,7 +10,7 @@ import 'package:test/test.dart'; import 'test_service.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { ChopperClient buildClient([ @@ -404,7 +404,7 @@ void main() { test('applyHeader', () { final req1 = applyHeader( - Request('GET', '/', baseUrl), + Request('GET', Uri.parse('/'), baseUrl), 'foo', 'bar', ); @@ -412,7 +412,7 @@ void main() { expect(req1.headers, equals({'foo': 'bar'})); final req2 = applyHeader( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), 'bar', 'foo', ); @@ -420,7 +420,7 @@ void main() { expect(req2.headers, equals({'foo': 'bar', 'bar': 'foo'})); final req3 = applyHeader( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), 'foo', 'foo', ); @@ -429,19 +429,20 @@ void main() { }); test('applyHeaders', () { - final req1 = applyHeaders(Request('GET', '/', baseUrl), {'foo': 'bar'}); + final req1 = + applyHeaders(Request('GET', Uri.parse('/'), baseUrl), {'foo': 'bar'}); expect(req1.headers, equals({'foo': 'bar'})); final req2 = applyHeaders( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), {'bar': 'foo'}, ); expect(req2.headers, equals({'foo': 'bar', 'bar': 'foo'})); final req3 = applyHeaders( - Request('GET', '/', baseUrl, headers: {'foo': 'bar'}), + Request('GET', Uri.parse('/'), baseUrl, headers: {'foo': 'bar'}), {'foo': 'foo'}, ); @@ -471,49 +472,56 @@ void main() { test('url concatenation', () async { expect( - Request.buildUri('foo', 'bar', {}).toString(), + Request.buildUri(Uri.parse('foo'), Uri.parse('bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo/', 'bar', {}).toString(), + Request.buildUri(Uri.parse('foo/'), Uri.parse('bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('foo'), Uri.parse('/bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('foo/', '/bar', {}).toString(), + Request.buildUri(Uri.parse('foo/'), Uri.parse('/bar'), {}).toString(), equals('foo/bar'), ); expect( - Request.buildUri('http://foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('http://foo'), Uri.parse('/bar'), {}) + .toString(), equals('http://foo/bar'), ); expect( - Request.buildUri('https://foo', '/bar', {}).toString(), + Request.buildUri(Uri.parse('https://foo'), Uri.parse('/bar'), {}) + .toString(), equals('https://foo/bar'), ); expect( - Request.buildUri('https://foo/', '/bar', {}).toString(), + Request.buildUri(Uri.parse('https://foo/'), Uri.parse('/bar'), {}) + .toString(), equals('https://foo/bar'), ); expect( - Request.buildUri('https://foo/', '/bar', {'abc': 'xyz'}).toString(), + Request.buildUri( + Uri.parse('https://foo/'), + Uri.parse('/bar'), + {'abc': 'xyz'}, + ).toString(), equals('https://foo/bar?abc=xyz'), ); expect( Request.buildUri( - 'https://foo/', - '/bar?first=123&second=456', + Uri.parse('https://foo/'), + Uri.parse('/bar?first=123&second=456'), { 'third': '789', 'fourth': '012', @@ -521,12 +529,81 @@ void main() { ).toString(), equals('https://foo/bar?first=123&second=456&third=789&fourth=012'), ); + + expect( + Request.buildUri( + Uri.parse('https://foo?first=123&second=456'), + Uri.parse('/bar'), + { + 'third': '789', + 'fourth': '012', + }, + ).toString(), + equals('https://foo/bar?third=789&fourth=012'), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo?first=123&second=456'), + Uri.parse('/bar?third=789&fourth=012'), + { + 'fifth': '345', + 'sixth': '678', + }, + ).toString(), + equals( + 'https://foo/bar?third=789&fourth=012&fifth=345&sixth=678', + ), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo.bar/foobar'), + Uri.parse('whatbar'), + {}, + ).toString(), + equals('https://foo.bar/foobar/whatbar'), + ); + + expect( + Request.buildUri( + Uri.parse('https://foo/bar?first=123&second=456'), + Uri.parse('https://bar/foo?fourth=789&fifth=012'), + {}, + ).toString(), + equals('https://bar/foo?fourth=789&fifth=012'), + ); + + expect( + Request('GET', Uri(path: '/bar'), Uri.parse('foo')).url.toString(), + equals('foo/bar'), + ); + + expect( + Request('GET', Uri(host: 'bar'), Uri.parse('foo')).url.toString(), + equals('foo/'), + ); + + expect( + Request('GET', Uri.https('bar'), Uri.parse('foo')).url.toString(), + equals('https://bar'), + ); + + expect( + Request( + 'GET', + Uri(scheme: 'https', host: 'bar', port: 666), + Uri.parse('foo'), + ).url.toString(), + equals('https://bar:666'), + ); }); test('BodyBytes', () { - final request = Request.uri( + final request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: [1, 2, 3], ).toHttpRequest(); @@ -534,9 +611,10 @@ void main() { }); test('BodyFields', () { - final request = Request.uri( + final request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: {'foo': 'bar'}, ).toHttpRequest(); @@ -545,9 +623,10 @@ void main() { test('Wrong body', () { try { - Request.uri( + Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: {'foo': 42}, ).toHttpRequest(); } on ArgumentError catch (e) { @@ -1179,4 +1258,53 @@ void main() { httpClient.close(); }); + + test('client baseUrl cannot contain query parameters', () { + expect( + () => ChopperClient( + baseUrl: Uri.http( + 'foo', + 'bar', + { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => ChopperClient( + baseUrl: Uri.parse('foo/bar?first=123'), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => ChopperClient( + baseUrl: Uri( + queryParameters: { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + expect( + () => ChopperClient( + baseUrl: Uri(query: 'first=123&second=456'), + ), + throwsA( + TypeMatcher(), + ), + ); + }); } diff --git a/chopper/test/client_test.dart b/chopper/test/client_test.dart index babb172d..88b0d2a1 100644 --- a/chopper/test/client_test.dart +++ b/chopper/test/client_test.dart @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:test/test.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( @@ -33,9 +33,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.get( - '/test/get', + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('get response')); @@ -59,10 +61,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.post( - '/test/post', + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('post response')); @@ -87,10 +91,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.put( - '/test/put', + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('put response')); @@ -115,10 +121,12 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.patch( - '/test/patch', + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, body: {'content': 'body'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('patch response')); @@ -142,9 +150,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.delete( - '/test/delete', + Uri( + path: '/test/delete', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('delete response')); @@ -167,9 +177,11 @@ void main() { final chopper = buildClient(httpClient); final response = await chopper.options( - '/test/get', + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), headers: {'int': '42'}, - parameters: {'key': 'val'}, ); expect(response.body, equals('get response')); diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 2fae6736..6b5d868c 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -7,7 +7,7 @@ import 'package:test/test.dart'; import 'test_service.dart'; -const baseUrl = 'http://localhost:8000'; +final baseUrl = Uri.parse('http://localhost:8000'); void main() { group('Converter', () { @@ -34,7 +34,12 @@ void main() { final converter = TestConverter(); final encoded = converter.convertRequest( - Request('GET', '/', baseUrl, body: _Converted('foo')), + Request( + 'GET', + Uri.parse('/'), + baseUrl, + body: _Converted('foo'), + ), ); expect(encoded.body is String, isTrue); diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index 16257dca..bffcd040 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -47,8 +47,9 @@ void main() { test('RequestInterceptorFunc', () async { final chopper = ChopperClient( interceptors: [ - (Request request) => - request.copyWith(path: '${request.url}/intercept'), + (Request request) => request.copyWith( + uri: request.uri.replace(path: '${request.uri.path}/intercept'), + ), ], services: [ HttpTestService.create(), @@ -184,8 +185,8 @@ void main() { final fakeRequest = Request( 'POST', - '/', - 'base', + Uri.parse('/'), + Uri.parse('base'), body: 'test', headers: {'foo': 'bar'}, ); @@ -270,8 +271,9 @@ class ResponseIntercept implements ResponseInterceptor { class RequestIntercept implements RequestInterceptor { @override - FutureOr onRequest(Request request) => - request.copyWith(path: '${request.url}/intercept'); + FutureOr onRequest(Request request) => request.copyWith( + uri: request.uri.replace(path: '${request.uri}/intercept'), + ); } class _Intercepted { diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index 1e461d2f..9bffea50 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -203,9 +203,10 @@ void main() { }); test('PartValue', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue('foo', 'bar'), PartValue('int', 42), @@ -219,9 +220,10 @@ void main() { test( 'PartFile', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValueFile('foo', 'test/multipart_test.dart'), PartValueFile>('int', [1, 2]), @@ -257,9 +259,10 @@ void main() { }); test('Multipart request non nullable', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue('int', 42), PartValueFile>('list int', [1, 2]), @@ -276,9 +279,10 @@ void main() { }); test('PartValue with MultipartFile directly', () async { - final req = await Request.uri( + final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValue( '', @@ -314,9 +318,10 @@ void main() { test('Throw exception', () async { expect( - () async => await Request.uri( + () async => await Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), parts: [ PartValueFile('', 123), ], diff --git a/chopper/test/request_test.dart b/chopper/test/request_test.dart index 6f886afd..45d9e2e0 100644 --- a/chopper/test/request_test.dart +++ b/chopper/test/request_test.dart @@ -1,42 +1,46 @@ // ignore_for_file: long-method import 'package:chopper/chopper.dart'; -import 'package:test/test.dart'; -import 'package:http/http.dart' as http; import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; void main() { group('Request', () { test('constructor produces a BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/'), + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')), isA(), ); }); test('method gets preserved in BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').method, + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')).method, equals('GET'), ); }); test('url is correctly parsed and set in BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').url, + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')).url, equals(Uri.parse('https://foo/bar')), ); expect( - Request('GET', '/bar?lorem=ipsum&dolor=123', 'https://foo/').url, + Request( + 'GET', + Uri.parse('/bar?lorem=ipsum&dolor=123'), + Uri.parse('https://foo/'), + ).url, equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), ); expect( Request( 'GET', - '/bar', - 'https://foo/', + Uri.parse('/bar'), + Uri.parse('https://foo/'), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -48,8 +52,8 @@ void main() { expect( Request( 'GET', - '/bar?first=sit&second=amet&first_list=a&first_list=b', - 'https://foo/', + Uri.parse('/bar?first=sit&second=amet&first_list=a&first_list=b'), + Uri.parse('https://foo/'), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -70,8 +74,8 @@ void main() { final Request request = Request( 'GET', - '/bar', - 'https://foo/', + Uri.parse('/bar'), + Uri.parse('https://foo/'), headers: headers, ); @@ -83,43 +87,48 @@ void main() { test('copyWith creates a BaseRequest', () { expect( - Request('GET', '/bar', 'https://foo/').copyWith(method: HttpMethod.Put), + Request('GET', Uri.parse('/bar'), Uri.parse('https://foo/')) + .copyWith(method: HttpMethod.Put), isA(), ); }); }); - group('Request.uri', () { + group('Request', () { test('constructor produces a BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')), + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')), isA(), ); }); test('method gets preserved in BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')).method, + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')).method, equals('GET'), ); }); test('url is correctly parsed and set in BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')).url, + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')).url, equals(Uri.parse('https://foo/bar')), ); expect( - Request.uri('GET', Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')) - .url, + Request( + 'GET', + Uri.parse('https://foo/bar?lorem=ipsum&dolor=123'), + Uri.parse(''), + ).url, equals(Uri.parse('https://foo/bar?lorem=ipsum&dolor=123')), ); expect( - Request.uri( + Request( 'GET', Uri.parse('https://foo/bar'), + Uri.parse(''), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -129,11 +138,12 @@ void main() { ); expect( - Request.uri( + Request( 'GET', Uri.parse( 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b', ), + Uri.parse(''), parameters: { 'lorem': 'ipsum', 'dolor': 123, @@ -144,6 +154,37 @@ void main() { 'https://foo/bar?first=sit&second=amet&first_list=a&first_list=b&lorem=ipsum&dolor=123&second_list=a&second_list=b', )), ); + + expect( + Request( + 'GET', + Uri.parse( + 'https://chopper.dev/test3', + ), + Uri.parse(''), + parameters: { + 'foo': 'bar', + 'foo_list': [ + 'one', + 'two', + 'three', + ], + 'user': { + 'name': 'john', + 'surname': 'doe', + }, + }, + ).url.toString(), + equals( + 'https://chopper.dev/test3' + '?foo=bar' + '&foo_list=one' + '&foo_list=two' + '&foo_list=three' + '&user.name=john' + '&user.surname=doe', + ), + ); }); test('headers are preserved in BaseRequest', () { @@ -152,9 +193,10 @@ void main() { 'accept': 'application/json; charset=utf-8', }; - final Request request = Request.uri( + final Request request = Request( 'GET', Uri.parse('https://foo/bar'), + Uri.parse(''), headers: headers, ); @@ -166,10 +208,67 @@ void main() { test('copyWith creates a BaseRequest', () { expect( - Request.uri('GET', Uri.parse('https://foo/bar')) + Request('GET', Uri.parse('https://foo/bar'), Uri.parse('')) .copyWith(method: HttpMethod.Put), isA(), ); }); }); + + test('request baseUri cannot contain query parameters', () { + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri.http( + 'foo', + 'bar', + { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri.parse('foo/bar?first=123'), + ), + throwsA( + TypeMatcher(), + ), + ); + + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri( + queryParameters: { + 'first': '123', + 'second': '456', + }, + ), + ), + throwsA( + TypeMatcher(), + ), + ); + expect( + () => Request( + 'GET', + Uri.parse('foo'), + Uri(query: 'first=123&second=456'), + ), + throwsA( + TypeMatcher(), + ), + ); + }); } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index fce9c168..97ce5db3 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -21,7 +21,7 @@ class _$HttpTestService extends HttpTestService { String id, { required String dynamicHeader, }) { - final String $url = '/test/get/${id}'; + final Uri $url = Uri.parse('/test/get/${id}'); final Map $headers = { 'test': dynamicHeader, }; @@ -36,7 +36,7 @@ class _$HttpTestService extends HttpTestService { @override Future> headTest() { - final String $url = '/test/head'; + final Uri $url = Uri.parse('/test/head'); final Request $request = Request( 'HEAD', $url, @@ -47,7 +47,7 @@ class _$HttpTestService extends HttpTestService { @override Future> optionsTest() { - final String $url = '/test/options'; + final Uri $url = Uri.parse('/test/options'); final Request $request = Request( 'OPTIONS', $url, @@ -58,7 +58,7 @@ class _$HttpTestService extends HttpTestService { @override Future>>> getStreamTest() { - final String $url = '/test/get'; + final Uri $url = Uri.parse('/test/get'); final Request $request = Request( 'GET', $url, @@ -69,7 +69,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getAll() { - final String $url = '/test'; + final Uri $url = Uri.parse('/test'); final Request $request = Request( 'GET', $url, @@ -80,7 +80,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getAllWithTrailingSlash() { - final String $url = '/test/'; + final Uri $url = Uri.parse('/test/'); final Request $request = Request( 'GET', $url, @@ -95,7 +95,7 @@ class _$HttpTestService extends HttpTestService { int? number, int? def = 42, }) { - final String $url = '/test/query'; + final Uri $url = Uri.parse('/test/query'); final Map $params = { 'name': name, 'int': number, @@ -112,7 +112,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest(Map query) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = query; final Request $request = Request( 'GET', @@ -128,7 +128,7 @@ class _$HttpTestService extends HttpTestService { Map query, { bool? test, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = {'test': test}; $params.addAll(query); final Request $request = Request( @@ -146,7 +146,7 @@ class _$HttpTestService extends HttpTestService { int? number, Map filters = const {}, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = { 'name': name, 'number': number, @@ -167,7 +167,7 @@ class _$HttpTestService extends HttpTestService { int? number, Map? filters, }) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = { 'name': name, 'number': number, @@ -184,7 +184,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getQueryMapTest5({Map? filters}) { - final String $url = '/test/query_map'; + final Uri $url = Uri.parse('/test/query_map'); final Map $params = filters ?? const {}; final Request $request = Request( 'GET', @@ -197,7 +197,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getBody(dynamic body) { - final String $url = '/test/get_body'; + final Uri $url = Uri.parse('/test/get_body'); final $body = body; final Request $request = Request( 'GET', @@ -210,7 +210,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postTest(String data) { - final String $url = '/test/post'; + final Uri $url = Uri.parse('/test/post'); final $body = data; final Request $request = Request( 'POST', @@ -223,7 +223,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postStreamTest(Stream> byteStream) { - final String $url = '/test/post'; + final Uri $url = Uri.parse('/test/post'); final $body = byteStream; final Request $request = Request( 'POST', @@ -239,7 +239,7 @@ class _$HttpTestService extends HttpTestService { String test, String data, ) { - final String $url = '/test/put/${test}'; + final Uri $url = Uri.parse('/test/put/${test}'); final $body = data; final Request $request = Request( 'PUT', @@ -252,7 +252,7 @@ class _$HttpTestService extends HttpTestService { @override Future> deleteTest(String id) { - final String $url = '/test/delete/${id}'; + final Uri $url = Uri.parse('/test/delete/${id}'); final Map $headers = { 'foo': 'bar', }; @@ -270,7 +270,7 @@ class _$HttpTestService extends HttpTestService { String id, String data, ) { - final String $url = '/test/patch/${id}'; + final Uri $url = Uri.parse('/test/patch/${id}'); final $body = data; final Request $request = Request( 'PATCH', @@ -283,7 +283,7 @@ class _$HttpTestService extends HttpTestService { @override Future> mapTest(Map map) { - final String $url = '/test/map'; + final Uri $url = Uri.parse('/test/map'); final $body = map; final Request $request = Request( 'POST', @@ -296,7 +296,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postForm(Map fields) { - final String $url = '/test/form/body'; + final Uri $url = Uri.parse('/test/form/body'); final $body = fields; final Request $request = Request( 'POST', @@ -312,7 +312,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postFormUsingHeaders(Map fields) { - final String $url = '/test/form/body'; + final Uri $url = Uri.parse('/test/form/body'); final Map $headers = { 'content-type': 'application/x-www-form-urlencoded', }; @@ -332,7 +332,7 @@ class _$HttpTestService extends HttpTestService { String foo, int bar, ) { - final String $url = '/test/form/body/fields'; + final Uri $url = Uri.parse('/test/form/body/fields'); final $body = { 'foo': foo, 'bar': bar, @@ -351,7 +351,7 @@ class _$HttpTestService extends HttpTestService { @override Future> forceJsonTest(Map map) { - final String $url = '/test/map/json'; + final Uri $url = Uri.parse('/test/map/json'); final $body = map; final Request $request = Request( 'POST', @@ -371,7 +371,7 @@ class _$HttpTestService extends HttpTestService { Map a, Map b, ) { - final String $url = '/test/multi'; + final Uri $url = Uri.parse('/test/multi'); final List $parts = [ PartValue>( '1', @@ -394,7 +394,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postFile(List bytes) { - final String $url = '/test/file'; + final Uri $url = Uri.parse('/test/file'); final List $parts = [ PartValueFile>( 'file', @@ -416,7 +416,7 @@ class _$HttpTestService extends HttpTestService { MultipartFile file, { String? id, }) { - final String $url = '/test/file'; + final Uri $url = Uri.parse('/test/file'); final List $parts = [ PartValue( 'id', @@ -439,7 +439,7 @@ class _$HttpTestService extends HttpTestService { @override Future> postListFiles(List files) { - final String $url = '/test/files'; + final Uri $url = Uri.parse('/test/files'); final List $parts = [ PartValueFile>( 'files', @@ -458,7 +458,7 @@ class _$HttpTestService extends HttpTestService { @override Future fullUrl() { - final String $url = 'https://test.com'; + final Uri $url = Uri.parse('https://test.com'); final Request $request = Request( 'GET', $url, @@ -469,7 +469,7 @@ class _$HttpTestService extends HttpTestService { @override Future>> listString() { - final String $url = '/test/list/string'; + final Uri $url = Uri.parse('/test/list/string'); final Request $request = Request( 'GET', $url, @@ -480,7 +480,7 @@ class _$HttpTestService extends HttpTestService { @override Future> noBody() { - final String $url = '/test/no-body'; + final Uri $url = Uri.parse('/test/no-body'); final Request $request = Request( 'POST', $url, @@ -495,7 +495,7 @@ class _$HttpTestService extends HttpTestService { String? bar, String? baz, }) { - final String $url = '/test/query_param_include_null_query_vars'; + final Uri $url = Uri.parse('/test/query_param_include_null_query_vars'); final Map $params = { 'foo': foo, 'bar': bar, @@ -513,7 +513,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingListQueryParam(List value) { - final String $url = '/test/list_query_param'; + final Uri $url = Uri.parse('/test/list_query_param'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -527,7 +527,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingListQueryParamWithBrackets( List value) { - final String $url = '/test/list_query_param_with_brackets'; + final Uri $url = Uri.parse('/test/list_query_param_with_brackets'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -541,7 +541,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParam(Map value) { - final String $url = '/test/map_query_param'; + final Uri $url = Uri.parse('/test/map_query_param'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -555,7 +555,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParamIncludeNullQueryVars( Map value) { - final String $url = '/test/map_query_param_include_null_query_vars'; + final Uri $url = Uri.parse('/test/map_query_param_include_null_query_vars'); final Map $params = {'value': value}; final Request $request = Request( 'GET', @@ -570,7 +570,7 @@ class _$HttpTestService extends HttpTestService { @override Future> getUsingMapQueryParamWithBrackets( Map value) { - final String $url = '/test/map_query_param_with_brackets'; + final Uri $url = Uri.parse('/test/map_query_param_with_brackets'); final Map $params = {'value': value}; final Request $request = Request( 'GET', diff --git a/chopper_built_value/test/converter_test.dart b/chopper_built_value/test/converter_test.dart index f47cd763..084636d5 100644 --- a/chopper_built_value/test/converter_test.dart +++ b/chopper_built_value/test/converter_test.dart @@ -26,9 +26,10 @@ void main() { group('BuiltValueConverter', () { test('convert request', () { - var request = Request.uri( + var request = Request( HttpMethod.Post, Uri.parse('https://foo/'), + Uri.parse(''), body: data, ); request = converter.convertRequest(request); @@ -69,9 +70,10 @@ void main() { }); test('has json headers', () { - var request = Request.uri( + var request = Request( HttpMethod.Get, Uri.parse('https://foo/'), + Uri.parse(''), body: data, ); request = converter.convertRequest(request); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 2ccd6cd3..67fb905f 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -174,7 +174,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ); final List blocks = [ - declareFinal(_urlVar, type: refer('String')).assign(url).statement, + declareFinal(_urlVar, type: refer('Uri')).assign(url).statement, ]; if (queries.isNotEmpty) { @@ -475,21 +475,26 @@ class ChopperGenerator extends GeneratorForAnnotation { if (path.startsWith('http://') || path.startsWith('https://')) { // if the request's url is already a fully qualified URL, we can use // as-is and ignore the baseUrl - return literal(path); + return _generateUri(path); } else if (path.isEmpty && baseUrl.isEmpty) { - return literal(''); + return _generateUri(''); } else { if (path.isNotEmpty && baseUrl.isNotEmpty && !baseUrl.endsWith('/') && !path.startsWith('/')) { - return literal('$baseUrl/$path'); + return _generateUri('$baseUrl/$path'); } - return literal('$baseUrl$path'); + return _generateUri('$baseUrl$path'); } } + Expression _generateUri(String url) => refer('Uri').newInstanceNamed( + 'parse', + [literal(url)], + ); + Expression _generateRequest( ConstantReader method, { bool hasBody = false, @@ -571,7 +576,9 @@ class ChopperGenerator extends GeneratorForAnnotation { ]; list.add( - refer('PartValueFile<${p.type.getDisplayString(withNullability: p.type.isNullable)}>') + refer('PartValueFile<${p.type.getDisplayString( + withNullability: p.type.isNullable, + )}>') .newInstance(params), ); }); diff --git a/example/bin/main_built_value.dart b/example/bin/main_built_value.dart index 9f87becc..7d7fe8cc 100644 --- a/example/bin/main_built_value.dart +++ b/example/bin/main_built_value.dart @@ -27,7 +27,7 @@ final client = MockClient((req) async { main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), converter: BuiltValueConverter(), errorConverter: BuiltValueConverter(), services: [ diff --git a/example/bin/main_json_serializable.dart b/example/bin/main_json_serializable.dart index 956014f5..d95b7f24 100644 --- a/example/bin/main_json_serializable.dart +++ b/example/bin/main_json_serializable.dart @@ -24,7 +24,7 @@ main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter, diff --git a/example/bin/main_json_serializable_squadron_worker_pool.dart b/example/bin/main_json_serializable_squadron_worker_pool.dart index 2c3bbb08..e9564899 100644 --- a/example/bin/main_json_serializable_squadron_worker_pool.dart +++ b/example/bin/main_json_serializable_squadron_worker_pool.dart @@ -126,7 +126,7 @@ Future main() async { final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter, diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index a68d4b5c..9b83b3fb 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}/'; + final Uri $url = Uri.parse('/resources/${id}/'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getBuiltListResources() { - final String $url = '/resources/list'; + final Uri $url = Uri.parse('/resources/list'); final Request $request = Request( 'GET', $url, @@ -40,7 +40,7 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $headers = { 'foo': 'bar', }; @@ -58,7 +58,7 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final String $url = '/resources'; + final Uri $url = Uri.parse('/resources'); final Map $headers = { if (name != null) 'name': name, }; diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index e9892bfc..7a60b5ab 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -18,7 +18,7 @@ class _$MyService extends MyService { @override Future> getResource(String id) { - final String $url = '/resources/${id}/'; + final Uri $url = Uri.parse('/resources/${id}/'); final Request $request = Request( 'GET', $url, @@ -29,7 +29,7 @@ class _$MyService extends MyService { @override Future>> getResources() { - final String $url = '/resources/all'; + final Uri $url = Uri.parse('/resources/all'); final Map $headers = { 'test': 'list', }; @@ -44,7 +44,7 @@ class _$MyService extends MyService { @override Future>> getMapResource(String id) { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $params = {'id': id}; final Request $request = Request( 'GET', @@ -57,7 +57,7 @@ class _$MyService extends MyService { @override Future> getTypedResource() { - final String $url = '/resources/'; + final Uri $url = Uri.parse('/resources/'); final Map $headers = { 'foo': 'bar', }; @@ -75,7 +75,7 @@ class _$MyService extends MyService { Resource resource, { String? name, }) { - final String $url = '/resources'; + final Uri $url = Uri.parse('/resources'); final Map $headers = { if (name != null) 'name': name, }; diff --git a/faq.md b/faq.md index fca603c6..f5d2b193 100644 --- a/faq.md +++ b/faq.md @@ -71,8 +71,8 @@ You may need to change the base URL of your network calls during runtime, for ex (Request request) async => SharedPreferences.containsKey('baseUrl') ? request.copyWith( - baseUrl: SharedPreferences.getString('baseUrl')) - : request + baseUri: Uri.parse(SharedPreferences.getString('baseUrl')) + ): request ... ``` @@ -109,7 +109,7 @@ abstract class ApiService extends ChopperService { } return http.Response(json.encode(result), 200); }), - baseUrl: 'https://mysite.com/api', + baseUrl: Uri.parse('https://mysite.com/api'), services: [ _$ApiService(), ], @@ -332,7 +332,7 @@ Future main() async { /// Instantiate a ChopperClient final chopper = ChopperClient( client: client, - baseUrl: 'http://localhost:8000', + baseUrl: Uri.parse('http://localhost:8000'), // bind your object factories here converter: converter, errorConverter: converter, From 9d1d86c80063deceb0c3f4e1161e346d3a892eb5 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 20 Nov 2022 09:30:21 +0000 Subject: [PATCH 41/61] :memo: Add Authenticator example (#386) --- faq.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/faq.md b/faq.md index f5d2b193..a2f31633 100644 --- a/faq.md +++ b/faq.md @@ -170,6 +170,57 @@ interceptors: [ The actual implementation of the algorithm above may vary based on how the backend API - more precisely the login and session handling - of your app looks like. +### Authorized HTTP requests using the special Authenticator interceptor + +Similar to OkHTTP's [authenticator](https://github.com/square/okhttp/blob/480c20e46bb1745e280e42607bbcc73b2c953d97/okhttp/src/main/kotlin/okhttp3/Authenticator.kt), +the idea here is to provide a reactive authentication in the event that an auth challenge is raised. It returns a +nullable Request that contains a possible update to the original Request to satisfy the authentication challenge. + +```dart +import 'dart:async' show FutureOr; +import 'dart:io' show HttpHeaders, HttpStatus; + +import 'package:chopper/chopper.dart'; + +/// This method returns a [Request] that includes credentials to satisfy an authentication challenge received in +/// [response]. It returns `null` if the challenge cannot be satisfied. +class MyAuthenticator extends Authenticator { + @override + FutureOr authenticate( + Request request, + Response response, [ + Request? originalRequest, + ]) async { + if (response.statusCode == HttpStatus.unauthorized) { + final String? newToken = await refreshToken(); + + if (newToken != null) { + return request.copyWith(headers: { + ...request.headers, + HttpHeaders.authorizationHeader: newToken, + }); + } + } + + return null; + } + + Future refreshToken() async { + /// Refresh the accessToken using refreshToken however needed. + /// This could be done either via an HTTP client, or a ChopperService, or a + /// repository could be a dependency. + /// This approach is intentionally not opinionated about how this works. + throw UnimplementedError(); + } +} + +/// When initializing your ChopperClient +final client = ChopperClient( + /// register your Authenticator here + authenticator: MyAuthenticator(), +); +``` + ## Decoding JSON using Isolates Sometimes you want to decode JSON outside the main thread in order to reduce janking. In this example we're going to go From 2775a735aea6fe4fb77e143f374359a0ada65f79 Mon Sep 17 00:00:00 2001 From: Erlang Parasu Date: Sun, 11 Dec 2022 19:17:35 +0800 Subject: [PATCH 42/61] Update getting-started.md (#391) --- getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getting-started.md b/getting-started.md index f7107ffa..fe9cbd01 100644 --- a/getting-started.md +++ b/getting-started.md @@ -41,7 +41,7 @@ part "YOUR_FILE.chopper.dart"; abstract class TodosListService extends ChopperService { // A helper method that helps instantiating the service. You can omit this method and use the generated class directly instead. - static TodosListService create([ChopperClient client]) => + static TodosListService create([ChopperClient? client]) => _$TodosListService(client); } ``` From 11a9ca50f55bebad7a7794c0a45bbde898aa1080 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 11 Dec 2022 12:06:02 +0000 Subject: [PATCH 43/61] Fix #388 Triggering Authenticator double encodes body in POST, PUT, PATCH (#390) fixes https://github.com/lejard-h/chopper/issues/388 --- chopper/lib/src/base.dart | 9 +- chopper/test/authenticator_test.dart | 392 +++++++++++++++++++++++++++ chopper/test/fake_authenticator.dart | 23 ++ 3 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 chopper/test/authenticator_test.dart create mode 100644 chopper/test/fake_authenticator.dart diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 624607b3..4fa556c2 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -293,9 +293,10 @@ class ChopperClient { ConvertRequest? requestConverter, ConvertResponse? responseConverter, }) async { - var req = await _interceptRequest( + final Request req = await _interceptRequest( await _handleRequestConverter(request, requestConverter), ); + _requestController.add(req); final streamRes = await httpClient.send(await req.toBaseRequest()); @@ -307,7 +308,11 @@ class ChopperClient { dynamic res = Response(response, response.body); if (authenticator != null) { - var updatedRequest = await authenticator!.authenticate(req, res, request); + final Request? updatedRequest = await authenticator!.authenticate( + request, + res, + request, + ); if (updatedRequest != null) { res = await send( diff --git a/chopper/test/authenticator_test.dart b/chopper/test/authenticator_test.dart new file mode 100644 index 00000000..8f4ebb20 --- /dev/null +++ b/chopper/test/authenticator_test.dart @@ -0,0 +1,392 @@ +import 'dart:convert' show jsonEncode; + +import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'fake_authenticator.dart'; + +void main() async { + final Uri baseUrl = Uri.parse('http://localhost:8000'); + + ChopperClient buildClient([http.Client? httpClient]) => ChopperClient( + baseUrl: baseUrl, + client: httpClient, + interceptors: [ + (Request req) => applyHeader(req, 'foo', 'bar'), + ], + converter: JsonConverter(), + authenticator: FakeAuthenticator(), + ); + + late bool authenticated; + final Map tested = { + 'unauthenticated': false, + 'authenticated': false, + }; + + setUp(() { + authenticated = false; + tested['unauthenticated'] = false; + tested['authenticated'] = false; + }); + + group('GET', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/get?key=val'), + ); + expect(request.method, equals('GET')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.get( + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/get?key=val'), + ); + expect(request.method, equals('GET')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.get( + Uri( + path: '/test/get', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); + + group('POST', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/post?key=val'), + ); + expect(request.method, equals('POST')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.post( + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/post?key=val'), + ); + expect(request.method, equals('POST')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.post( + Uri( + path: '/test/post', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); + + group('PUT', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/put?key=val'), + ); + expect(request.method, equals('PUT')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.put( + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/put?key=val'), + ); + expect(request.method, equals('PUT')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.put( + Uri( + path: '/test/put', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); + + group('PATCH', () { + test('authorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/patch?key=val'), + ); + expect(request.method, equals('PATCH')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.patch( + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + + test('unauthorized', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$baseUrl/test/patch?key=val'), + ); + expect(request.method, equals('PATCH')); + expect(request.headers['foo'], equals('bar')); + expect(request.headers['int'], equals('42')); + expect( + request.body, + jsonEncode( + { + 'name': 'john', + 'surname': 'doe', + }, + ), + ); + + if (!authenticated) { + tested['unauthenticated'] = true; + authenticated = true; + + return http.Response('unauthorized', 401); + } else { + tested['authenticated'] = true; + expect(request.headers['authorization'], equals('some_fake_token')); + } + + return http.Response('ok', 200); + }); + + final chopper = buildClient(httpClient); + final response = await chopper.patch( + Uri( + path: '/test/patch', + queryParameters: {'key': 'val'}, + ), + headers: {'int': '42'}, + body: { + 'name': 'john', + 'surname': 'doe', + }, + ); + + expect(response.body, equals('ok')); + expect(response.statusCode, equals(200)); + expect(tested['authenticated'], equals(true)); + expect(tested['unauthenticated'], equals(true)); + + httpClient.close(); + }); + }); +} diff --git a/chopper/test/fake_authenticator.dart b/chopper/test/fake_authenticator.dart new file mode 100644 index 00000000..22de9356 --- /dev/null +++ b/chopper/test/fake_authenticator.dart @@ -0,0 +1,23 @@ +import 'dart:async' show FutureOr; + +import 'package:chopper/chopper.dart'; + +class FakeAuthenticator extends Authenticator { + @override + FutureOr authenticate( + Request request, + Response response, [ + Request? originalRequest, + ]) async { + if (response.statusCode == 401) { + return request.copyWith( + headers: { + ...request.headers, + 'authorization': 'some_fake_token', + }, + ); + } + + return null; + } +} From bdbe2102f5ef8fbe0a26097d2fd3977e34559be1 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 11 Dec 2022 12:56:49 +0000 Subject: [PATCH 44/61] Add image upload test (#392) --- chopper/pubspec.yaml | 1 + chopper/test/multipart_test.dart | 24 ++++++++++++++++++++++++ chopper/test/test_service.chopper.dart | 19 +++++++++++++++++++ chopper/test/test_service.dart | 6 ++++++ 4 files changed, 50 insertions(+) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 51d7abcd..4578345c 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -21,5 +21,6 @@ dev_dependencies: http_parser: ^4.0.0 lints: ^2.0.0 test: ^1.16.4 + transparent_image: ^2.0.0 chopper_generator: path: ../chopper_generator diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index 9bffea50..ebb8af90 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:http_parser/http_parser.dart'; import 'package:test/test.dart'; +import 'package:transparent_image/transparent_image.dart'; import 'test_service.dart'; @@ -66,6 +67,29 @@ void main() { chopper.dispose(); }); + + test('image', () async { + final httpClient = MockClient((http.Request req) async { + final String body = String.fromCharCodes(req.bodyBytes); + + expect(req.headers['Content-Type'], contains('multipart/form-data;')); + expect(body, contains('content-type: application/octet-stream')); + expect(body, contains('content-disposition: form-data; name="image"')); + expect( + body, + contains(String.fromCharCodes(kTransparentImage)), + ); + + return http.Response('ok', 200); + }); + + final chopper = ChopperClient(client: httpClient); + final service = HttpTestService.create(chopper); + + await service.postImage(kTransparentImage); + + chopper.dispose(); + }); }); test('file with MultipartFile', () async { diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index 97ce5db3..f93398a5 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -411,6 +411,25 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> postImage(List imageData) { + final Uri $url = Uri.parse('/test/image'); + final List $parts = [ + PartValueFile>( + 'image', + imageData, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + @override Future> postMultipartFile( MultipartFile file, { diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 7d19418c..6257f43a 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -119,6 +119,12 @@ abstract class HttpTestService extends ChopperService { @PartFile('file') List bytes, ); + @Post(path: 'image') + @multipart + Future postImage( + @PartFile('image') List imageData, + ); + @Post(path: 'file') @multipart Future postMultipartFile( From b6ea960a523cf2599f13c940fc3e9bf98ec4661d Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Tue, 13 Dec 2022 16:09:53 +0300 Subject: [PATCH 45/61] Release 5.2.0 (#394) Co-authored-by: Ivan Terekhin <231950+JEuler@users.noreply.github.com> --- .gitignore | 3 ++- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 3 +++ chopper_generator/pubspec.yaml | 2 +- example/pubspec.yaml | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 08fe361f..e752ffab 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ doc/api/ coverage chopper/doc/ .vscode/ -pubspec.temp.yaml \ No newline at end of file +pubspec.temp.yaml +.DS_Store diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index 56519d03..ff75d44a 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## 5.2.0 + +- Replaced the String based path with Uri +- Fix for Authenticator body rewrite ## 5.1.0 diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 4578345c..e9ffa8cf 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.1.0 +version: 5.2.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 185ca6c8..4bdfebed 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,4 +1,7 @@ # Changelog +## 5.2.0 + +- Replaced the String based path with Uri ## 5.1.0 diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index a0fe86dc..0e34c7ec 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.1.0 +version: 5.2.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 65be75a6..47051983 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_example description: Example usage of the Chopper package -version: 0.0.2 +version: 0.0.3 documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard From 1719c833b77df8cd9a1d0157f6cc27777800adb8 Mon Sep 17 00:00:00 2001 From: Ivan Terekhin Date: Wed, 14 Dec 2022 11:15:47 +0300 Subject: [PATCH 46/61] 6.0.0 (#397) --- chopper/CHANGELOG.md | 7 ++++++- chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 7 ++++++- chopper_generator/pubspec.yaml | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index ff75d44a..32f0af43 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,7 +1,12 @@ # Changelog +## 6.0.0 + +- Replaced the String based path with Uri (BREAKING CHANGE) +- Fix for Authenticator body rewrite + ## 5.2.0 -- Replaced the String based path with Uri +- Replaced the String based path with Uri (BREAKING CHANGE) - Fix for Authenticator body rewrite ## 5.1.0 diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index e9ffa8cf..ad1ac78f 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.2.0 +version: 6.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 4bdfebed..39fc57cd 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,7 +1,12 @@ # Changelog + +## 6.0.0 + +- Replaced the String based path with Uri (BREAKING CHANGE) + ## 5.2.0 -- Replaced the String based path with Uri +- Replaced the String based path with Uri (BREAKING CHANGE) ## 5.1.0 diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 0e34c7ec..c4ed6cfe 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,6 +1,6 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 5.2.0 +version: 6.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper @@ -11,7 +11,7 @@ dependencies: analyzer: '>=4.4.0 <6.0.0' build: ^2.0.0 built_collection: ^5.0.0 - chopper: ^5.0.0 + chopper: ^6.0.0 code_builder: ^4.3.0 dart_style: ^2.0.0 logging: ^1.0.0 From 7453582a459b77682b3b3a11f17d9473e3951a1e Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Fri, 6 Jan 2023 16:25:10 +0100 Subject: [PATCH 47/61] Configurable HttpLoggingInterceptor (#399) * Extracted the http logging interceptor Make http logging interceptor configurable (none, basic, headers, body) * Minor tweaks Updated documentation * Simplified onRequest body and startMessage logic. Made onResponse more consistent with onRequest. --- chopper/lib/chopper.dart | 1 + chopper/lib/src/http_logging_interceptor.dart | 173 ++++++++++ chopper/lib/src/interceptor.dart | 52 --- .../test/http_logging_interceptor_test.dart | 302 ++++++++++++++++++ chopper/test/interceptors_test.dart | 51 --- interceptors.md | 11 + 6 files changed, 487 insertions(+), 103 deletions(-) create mode 100644 chopper/lib/src/http_logging_interceptor.dart create mode 100644 chopper/test/http_logging_interceptor_test.dart diff --git a/chopper/lib/chopper.dart b/chopper/lib/chopper.dart index e7230361..2290febd 100644 --- a/chopper/lib/chopper.dart +++ b/chopper/lib/chopper.dart @@ -9,6 +9,7 @@ export 'src/base.dart'; export 'src/constants.dart'; export 'src/extensions.dart'; export 'src/interceptor.dart'; +export 'src/http_logging_interceptor.dart'; export 'src/request.dart'; export 'src/response.dart'; export 'src/utils.dart' hide mapToQuery; diff --git a/chopper/lib/src/http_logging_interceptor.dart b/chopper/lib/src/http_logging_interceptor.dart new file mode 100644 index 00000000..0be43965 --- /dev/null +++ b/chopper/lib/src/http_logging_interceptor.dart @@ -0,0 +1,173 @@ +import 'dart:async'; + +import 'package:chopper/src/interceptor.dart'; +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; +import 'package:chopper/src/utils.dart'; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; + +enum Level { + /// No logs. + none, + + /// Logs request and response lines. + /// + /// Example: + /// ``` + /// --> POST https://foo.bar/greeting (3-byte body) + /// + /// <-- 200 OK POST https://foo.bar/greeting (6-byte body) + /// ``` + basic, + + /// Logs request and response lines and their respective headers. + /// + /// Example: + /// ``` + /// --> POST https://foo.bar/greeting + /// content-type: plain/text + /// content-length: 3 + /// --> END POST + /// + /// <-- 200 OK POST https://foo.bar/greeting + /// content-type: plain/text + /// content-length: 6 + /// <-- END HTTP + /// ``` + headers, + + /// Logs request and response lines and their respective headers and bodies (if present). + /// + /// Example: + /// ``` + /// --> POST https://foo.bar/greeting + /// content-type: plain/text + /// content-length: 3 + /// + /// Hi? + /// --> END POST https://foo.bar/greeting + /// + /// <-- 200 OK POST https://foo.bar/greeting + /// content-type: plain/text + /// content-length: 6 + /// + /// Hello! + /// <-- END HTTP + /// ``` + body, +} + +/// A [RequestInterceptor] and [ResponseInterceptor] implementation which logs +/// HTTP request and response data. +/// +/// Log levels can be set by applying [level] for more fine grained control +/// over amount of information being logged. +/// +/// **Warning:** Log messages written by this interceptor have the potential to +/// leak sensitive information, such as `Authorization` headers and user data +/// in response bodies. This interceptor should only be used in a controlled way +/// or in a non-production environment. +@immutable +class HttpLoggingInterceptor + implements RequestInterceptor, ResponseInterceptor { + const HttpLoggingInterceptor({this.level = Level.body}) + : _logBody = level == Level.body, + _logHeaders = level == Level.body || level == Level.headers; + + final Level level; + final bool _logBody; + final bool _logHeaders; + + @override + FutureOr onRequest(Request request) async { + if (level == Level.none) return request; + final http.BaseRequest base = await request.toBaseRequest(); + + String startRequestMessage = '--> ${base.method} ${base.url.toString()}'; + String bodyMessage = ''; + if (base is http.Request) { + if (base.body.isNotEmpty) { + bodyMessage = base.body; + + if (!_logHeaders) { + startRequestMessage += ' (${base.bodyBytes.length}-byte body)'; + } + } + } + + // Always start on a new line + chopperLogger.info(''); + chopperLogger.info(startRequestMessage); + + if (_logHeaders) { + base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); + + if (base.contentLength != null && + base.headers['content-length'] == null) { + chopperLogger.info('content-length: ${base.contentLength}'); + } + } + + if (_logBody && bodyMessage.isNotEmpty) { + chopperLogger.info(''); + chopperLogger.info(bodyMessage); + } + + if (_logHeaders || _logBody) { + chopperLogger.info('--> END ${base.method}'); + } + + return request; + } + + @override + FutureOr onResponse(Response response) { + if (level == Level.none) return response; + final base = response.base; + + String bytes = ''; + String reasonPhrase = response.statusCode.toString(); + String bodyMessage = ''; + if (base is http.Response) { + if (base.reasonPhrase != null) { + reasonPhrase += + ' ${base.reasonPhrase != reasonPhrase ? base.reasonPhrase : ''}'; + } + + if (base.body.isNotEmpty) { + bodyMessage = base.body; + + if (!_logBody && !_logHeaders) { + bytes = ' (${response.bodyBytes.length}-byte body)'; + } + } + } + + // Always start on a new line + chopperLogger.info(''); + chopperLogger.info( + '<-- $reasonPhrase ${base.request?.method} ${base.request?.url.toString()}$bytes', + ); + + if (_logHeaders) { + base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); + + if (base.contentLength != null && + base.headers['content-length'] == null) { + chopperLogger.info('content-length: ${base.contentLength}'); + } + } + + if (_logBody && bodyMessage.isNotEmpty) { + chopperLogger.info(''); + chopperLogger.info(bodyMessage); + } + + if (_logBody || _logHeaders) { + chopperLogger.info('<-- END HTTP'); + } + + return response; + } +} diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index e5f7bbe0..0e123237 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -158,58 +158,6 @@ class CurlInterceptor implements RequestInterceptor { } } -/// A [RequestInterceptor] and [ResponseInterceptor] implementation which logs -/// HTTP request and response data. -/// -/// **Warning:** Log messages written by this interceptor have the potential to -/// leak sensitive information, such as `Authorization` headers and user data -/// in response bodies. This interceptor should only be used in a controlled way -/// or in a non-production environment. -@immutable -class HttpLoggingInterceptor - implements RequestInterceptor, ResponseInterceptor { - @override - FutureOr onRequest(Request request) async { - final http.BaseRequest base = await request.toBaseRequest(); - chopperLogger.info('--> ${base.method} ${base.url.toString()}'); - base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); - - String bytes = ''; - if (base is http.Request) { - final body = base.body; - if (body.isNotEmpty) { - chopperLogger.info(body); - bytes = ' (${base.bodyBytes.length}-byte body)'; - } - } - - chopperLogger.info('--> END ${base.method}$bytes'); - - return request; - } - - @override - FutureOr onResponse(Response response) { - final http.BaseRequest? base = response.base.request; - chopperLogger.info('<-- ${response.statusCode} ${base!.url.toString()}'); - - response.base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); - - String bytes = ''; - if (response.base is http.Response) { - final resp = response.base as http.Response; - if (resp.body.isNotEmpty) { - chopperLogger.info(resp.body); - bytes = ' (${response.bodyBytes.length}-byte body)'; - } - } - - chopperLogger.info('--> END ${base.method}$bytes'); - - return response; - } -} - /// A [Converter] implementation that calls [json.encode] on [Request]s and /// [json.decode] on [Response]s using the [dart:convert](https://api.dart.dev/stable/2.10.3/dart-convert/dart-convert-library.html) /// package's [utf8] and [json] utilities. diff --git a/chopper/test/http_logging_interceptor_test.dart b/chopper/test/http_logging_interceptor_test.dart new file mode 100644 index 00000000..9a2d49f4 --- /dev/null +++ b/chopper/test/http_logging_interceptor_test.dart @@ -0,0 +1,302 @@ +import 'package:chopper/src/http_logging_interceptor.dart'; +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; +import 'package:chopper/src/utils.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; + +void main() { + final fakeRequest = Request( + 'POST', + Uri.parse('/'), + Uri.parse('base'), + body: 'test', + headers: {'foo': 'bar'}, + ); + + group('http logging requests', () { + test('Http logger interceptor none level request', () async { + final logger = HttpLoggingInterceptor(level: Level.none); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onRequest(fakeRequest); + + expect( + logs, + equals( + [], + ), + ); + }); + + test('Http logger interceptor basic level request', () async { + final logger = HttpLoggingInterceptor(level: Level.basic); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onRequest(fakeRequest); + + expect( + logs, + equals( + [ + '', + '--> POST base/ (4-byte body)', + ], + ), + ); + }); + + test('Http logger interceptor basic level request', () async { + final logger = HttpLoggingInterceptor(level: Level.headers); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onRequest(fakeRequest); + + expect( + logs, + equals( + [ + '', + '--> POST base/', + 'foo: bar', + 'content-type: text/plain; charset=utf-8', + 'content-length: 4', + '--> END POST', + ], + ), + ); + }); + + test('Http logger interceptor body level request', () async { + final logger = HttpLoggingInterceptor(level: Level.body); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onRequest(fakeRequest); + + expect( + logs, + equals( + [ + '', + '--> POST base/', + 'foo: bar', + 'content-type: text/plain; charset=utf-8', + 'content-length: 4', + '', + 'test', + '--> END POST', + ], + ), + ); + }); + }); + + group('http logging interceptor response logging', () { + late Response fakeResponse; + + setUp(() async { + fakeResponse = Response( + http.Response( + 'responseBodyBase', + 200, + headers: {'foo': 'bar'}, + request: await fakeRequest.toBaseRequest(), + ), + 'responseBody', + ); + }); + + test('Http logger interceptor none level response', () async { + final logger = HttpLoggingInterceptor(level: Level.none); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [], + ), + ); + }); + + test('Http logger interceptor basic level response', () async { + final logger = HttpLoggingInterceptor(level: Level.basic); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [ + '', + '<-- 200 POST base/ (16-byte body)', + ], + ), + ); + }); + + test('Http logger interceptor headers level response', () async { + final logger = HttpLoggingInterceptor(level: Level.headers); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [ + '', + '<-- 200 POST base/', + 'foo: bar', + 'content-length: 16', + '<-- END HTTP', + ], + ), + ); + }); + + test('Http logger interceptor body level response', () async { + final logger = HttpLoggingInterceptor(level: Level.body); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [ + '', + '<-- 200 POST base/', + 'foo: bar', + 'content-length: 16', + '', + 'responseBodyBase', + '<-- END HTTP', + ], + ), + ); + }); + }); + + group('headers content-length not overridden', () { + late Response fakeResponse; + + setUp(() async { + fakeResponse = Response( + http.Response( + 'responseBodyBase', + 200, + headers: { + 'foo': 'bar', + 'content-length': '42', + }, + request: await fakeRequest.toBaseRequest(), + ), + 'responseBody', + ); + }); + + test('request header level content-length', () async { + final logger = HttpLoggingInterceptor(level: Level.headers); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + + await logger.onRequest(fakeRequest + .copyWith(headers: {...fakeRequest.headers, 'content-length': '42'})); + + expect( + logs, + equals( + [ + '', + '--> POST base/', + 'foo: bar', + 'content-length: 42', + 'content-type: text/plain; charset=utf-8', + '--> END POST', + ], + ), + ); + }); + + test('request body level content-length', () async { + final logger = HttpLoggingInterceptor(level: Level.body); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + + await logger.onRequest(fakeRequest + .copyWith(headers: {...fakeRequest.headers, 'content-length': '42'})); + + expect( + logs, + equals( + [ + '', + '--> POST base/', + 'foo: bar', + 'content-length: 42', + 'content-type: text/plain; charset=utf-8', + '', + 'test', + '--> END POST', + ], + ), + ); + }); + + test('response header level content-length', () async { + final logger = HttpLoggingInterceptor(level: Level.headers); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [ + '', + '<-- 200 POST base/', + 'foo: bar', + 'content-length: 42', + '<-- END HTTP', + ], + ), + ); + }); + test('response body level content-length', () async { + final logger = HttpLoggingInterceptor(level: Level.body); + + final logs = []; + chopperLogger.onRecord.listen((r) => logs.add(r.message)); + await logger.onResponse(fakeResponse); + + expect( + logs, + equals( + [ + '', + '<-- 200 POST base/', + 'foo: bar', + 'content-length: 42', + '', + 'responseBodyBase', + '<-- END HTTP', + ], + ), + ); + }); + }); +} diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index bffcd040..0584dcc5 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -204,57 +204,6 @@ void main() { ), ); }); - - test('Http logger interceptor request', () async { - final logger = HttpLoggingInterceptor(); - - final logs = []; - chopperLogger.onRecord.listen((r) => logs.add(r.message)); - await logger.onRequest(fakeRequest); - - expect( - logs, - equals( - [ - '--> POST base/', - 'foo: bar', - 'content-type: text/plain; charset=utf-8', - 'test', - '--> END POST (4-byte body)', - ], - ), - ); - }); - - test('Http logger interceptor response', () async { - final logger = HttpLoggingInterceptor(); - - final fakeResponse = Response( - http.Response( - 'responseBodyBase', - 200, - headers: {'foo': 'bar'}, - request: await fakeRequest.toBaseRequest(), - ), - 'responseBody', - ); - - final logs = []; - chopperLogger.onRecord.listen((r) => logs.add(r.message)); - await logger.onResponse(fakeResponse); - - expect( - logs, - equals( - [ - '<-- 200 base/', - 'foo: bar', - 'responseBodyBase', - '--> END POST (16-byte body)', - ], - ), - ); - }); }); } diff --git a/interceptors.md b/interceptors.md index 2aadab1b..b8121023 100644 --- a/interceptors.md +++ b/interceptors.md @@ -35,3 +35,14 @@ final chopper = ChopperClient( * [CurlInterceptor](https://pub.dev/documentation/chopper/latest/chopper/CurlInterceptor-class.html) * [HttpLoggingInterceptor](https://pub.dev/documentation/chopper/latest/chopper/HttpLoggingInterceptor-class.html) +Both the `CurlInterceptor` and `HttpLoggingInterceptor` use the dart [logging package](https://pub.dev/packages/logging). +In order to see logging in console the logging package also needs to be added to your project and configured. + +For example: +```dart +Logger.root.level = Level.ALL; // defaults to Level.INFO +Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); +}); +``` + From fa73de364efde7b3cb27cf7ea8e65e3f31842614 Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Sun, 8 Jan 2023 15:58:46 +0100 Subject: [PATCH 48/61] Made apply headers field name case insensitive. (#400) * Made applyHeaders field name case insensitive. Added tests for applyHeaders * fixed quotes lint check --- chopper/lib/src/utils.dart | 18 ++-- chopper/test/converter_test.dart | 14 +++ chopper/test/utils_test.dart | 164 +++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 7 deletions(-) diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index 299ddead..bd9ab4cc 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:chopper/chopper.dart'; import 'package:logging/logging.dart'; @@ -39,13 +41,15 @@ Request applyHeaders( Map headers, { bool override = true, }) { - final Map headersCopy = {...request.headers}; - - for (String key in headers.keys) { - String? value = headers[key]; - if (value == null) continue; - if (!override && headersCopy.containsKey(key)) continue; - headersCopy[key] = value; + final LinkedHashMap headersCopy = LinkedHashMap( + equals: (a, b) => a.toLowerCase() == b.toLowerCase(), + hashCode: (e) => e.toLowerCase().hashCode, + ); + headersCopy.addAll(request.headers); + + for (final entry in headers.entries) { + if (!override && headersCopy.containsKey(entry.key)) continue; + headersCopy[entry.key] = entry.value; } return request.copyWith(headers: headersCopy); diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index 6b5d868c..d02d0a81 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -112,6 +112,20 @@ void main() { expect(converted.body, equals({'foo': 'bar'})); }); }); + + test('respects content-type headers', () { + final jsonConverter = JsonConverter(); + final testRequest = Request( + 'POST', + Uri.parse('foo'), + Uri.parse('bar'), + headers: {'Content-Type': 'application/vnd.api+json'}, + ); + + final result = jsonConverter.convertRequest(testRequest); + + expect(result.headers['content-type'], 'application/vnd.api+json'); + }); } class TestConverter implements Converter { diff --git a/chopper/test/utils_test.dart b/chopper/test/utils_test.dart index bf3f8e6f..63a35084 100644 --- a/chopper/test/utils_test.dart +++ b/chopper/test/utils_test.dart @@ -1,3 +1,4 @@ +import 'package:chopper/src/request.dart'; import 'package:chopper/src/utils.dart'; import 'package:test/test.dart'; @@ -563,4 +564,167 @@ void main() { ), ); }); + + Request createRequest(Map headers) => Request( + 'POST', + Uri.parse('foo'), + Uri.parse('bar'), + headers: headers, + ); + + group('applyHeader tests', () { + test('request apply single header', () { + final testRequest = createRequest({}); + + final result = applyHeader(testRequest, 'foo', 'bar'); + + expect(result.headers['foo'], 'bar'); + }); + + test('request apply single header overrides existing', () { + final testRequest = createRequest({'foo': 'bar'}); + + final result = applyHeader(testRequest, 'foo', 'whut'); + + expect(result.headers['foo'], 'whut'); + }); + + test( + 'request apply single header overrides existing field name case insensitive', + () { + final testRequest = createRequest({'Foo': 'bar'}); + + final result = applyHeader(testRequest, 'foo', 'whut'); + + expect(result.headers['foo'], 'whut'); + }, + ); + + test('request apply single header doesn\'t overrides existing', () { + final testRequest = createRequest({'foo': 'bar'}); + + final result = applyHeader(testRequest, 'foo', 'whut', override: false); + + expect(result.headers['foo'], 'bar'); + }); + + test( + 'request apply single header doesn\'t overrides existing field name case insensitive', + () { + final testRequest = createRequest({'Foo': 'bar'}); + + final result = applyHeader(testRequest, 'foo', 'whut', override: false); + + expect(result.headers['Foo'], 'bar'); + }, + ); + }); + + group('applyHeaders tests', () { + test('request apply headers', () { + final testRequest = createRequest({}); + + final result = applyHeaders(testRequest, {'foo': 'bar'}); + + expect(result.headers['foo'], 'bar'); + }); + + test('request apply headers overrides existing', () { + final testRequest = createRequest({'foo': 'bar'}); + + final result = applyHeaders(testRequest, {'foo': 'whut'}); + + expect(result.headers['foo'], 'whut'); + }); + + test( + 'request apply headers overrides existing field name case insensitive', + () { + final testRequest = createRequest({'Foo': 'bar'}); + + final result = applyHeaders(testRequest, {'foo': 'whut'}); + + expect(result.headers['foo'], 'whut'); + }, + ); + + test('request apply headers doesn\'t overrides existing', () { + final testRequest = createRequest({'foo': 'bar'}); + + final result = + applyHeaders(testRequest, {'foo': 'whut'}, override: false); + + expect(result.headers['foo'], 'bar'); + }); + + test( + 'request apply headers doesn\'t overrides existing field name case insensitive', + () { + final testRequest = createRequest({'Foo': 'bar'}); + + final result = + applyHeaders(testRequest, {'foo': 'whut'}, override: false); + + expect(result.headers['Foo'], 'bar'); + }, + ); + + test( + 'request apply headers multiple headers with override false', + () { + final testRequest = createRequest( + { + 'Foo': 'bar', + 'tomato': 'apple', + 'phone': 'tablet', + }, + ); + + final result = applyHeaders( + testRequest, + { + 'foo': 'whut', + 'phone': 'computer', + 'chair': 'table', + }, + override: false, + ); + + expect(result.headers['Foo'], 'bar'); + expect(result.headers['tomato'], 'apple'); + expect(result.headers['chair'], 'table'); + expect(result.headers['phone'], 'tablet'); + expect(result.headers.length, 4); + }, + ); + + test( + 'request apply headers multiple headers with override true', + () { + final testRequest = createRequest( + { + 'Foo': 'bar', + 'tomato': 'apple', + 'phone': 'tablet', + }, + ); + + final result = applyHeaders( + testRequest, + { + 'foo': 'whut', + 'phone': 'computer', + 'chair': 'table', + }, + override: true, + ); + + expect(result.headers['Foo'], 'whut'); + expect(result.headers['tomato'], 'apple'); + expect(result.headers['chair'], 'table'); + expect(result.headers['phone'], 'computer'); + expect(result.headers.length, 4); + }, + ); + }); } From bd3d7cc38ff9bb458bd29695ffaeaf4b7e914b18 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 9 Jan 2023 09:43:49 +0100 Subject: [PATCH 49/61] :bulb: Clean up code in comments (#403) --- chopper/lib/src/annotations.dart | 6 ++---- chopper/lib/src/utils.dart | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index 5ddda966..e7fa787c 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -328,6 +328,7 @@ typedef ConvertResponse = FutureOr Function(Response response); /// ) /// Future> getTodo(@Path("id")); /// } +/// ``` @immutable class FactoryConverter { final ConvertRequest? request; @@ -365,7 +366,6 @@ class Field { /// @Post(path: '/something') /// Future fetch(@FieldMap List> query); /// ``` -/// @immutable class FieldMap { const FieldMap(); @@ -405,7 +405,6 @@ class Part { /// @Multipart /// Future fetch(@PartMap() List query); /// ``` -/// @immutable class PartMap { const PartMap(); @@ -413,7 +412,7 @@ class PartMap { /// Use [PartFile] to define a file field for a [Multipart] request. /// -/// ``` +/// ```dart /// @Post(path: 'file') /// @multipart /// Future postFile(@PartFile('file') List bytes); @@ -437,7 +436,6 @@ class PartFile { /// @Multipart /// Future fetch(@PartFileMap() List query); /// ``` -/// @immutable class PartFileMap { const PartFileMap(); diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index bd9ab4cc..656c637c 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -32,8 +32,8 @@ Request applyHeader( /// /// ```dart /// final newRequest = applyHeaders(request, { -/// 'Authorization': 'Bearer ', -/// 'Content-Type': 'application/json', +/// 'Authorization': 'Bearer ', +/// 'Content-Type': 'application/json', /// }); /// ``` Request applyHeaders( From a910ede73256e580fbcab71d78addea98846c90a Mon Sep 17 00:00:00 2001 From: Mark Asselin Date: Thu, 26 Jan 2023 01:20:35 -0800 Subject: [PATCH 50/61] Fix dead link on FAQs page (#404) --- faq.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/faq.md b/faq.md index a2f31633..796a1eac 100644 --- a/faq.md +++ b/faq.md @@ -80,8 +80,7 @@ You may need to change the base URL of your network calls during runtime, for ex Chopper is built on top of `http` package. -So, one can just use the mocking API of the HTTP package. -https://pub.dev/documentation/http/latest/testing/MockClient-class.html +So, one can just use the mocking API of the HTTP package. See the documentation for the [http.testing library](https://pub.dev/documentation/http/latest/http.testing/http.testing-library.html) and for the [MockClient class](https://pub.dev/documentation/http/latest/http.testing/MockClient-class.html). Also, you can follow this code by [ozburo](https://github.com/ozburo): From adfb1c5b1bd9e228ae21f89fa3620f4befb1a04d Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Thu, 2 Feb 2023 16:22:29 +0100 Subject: [PATCH 51/61] Updated chopper version to 6.0.0 for chopper_built_value (#406) --- chopper_built_value/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 0bd63c23..2275d2de 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: built_value: ^8.0.0 built_collection: ^5.0.0 - chopper: ^5.0.0 + chopper: ^6.0.0 http: ^0.13.0 dev_dependencies: From 253d69a49ae0c5dd9ada9275a3188d2334e6cabf Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 22 Feb 2023 09:53:14 +0000 Subject: [PATCH 52/61] #290 implement Equatable (#410) * #290 implement Equatable * :heavy_plus_sign: add faker and data_fixture_dart as dev dependencies * :white_check_mark: add Request equatability test * :white_check_mark: add Response equatability test * :fire: Remove some obsolete equatables * :fire: Remove some obsolete equatables * :white_check_mark: add PartValue equatability test --- chopper/lib/src/request.dart | 25 +- chopper/lib/src/response.dart | 10 +- chopper/lib/src/utils.dart | 9 +- chopper/pubspec.yaml | 5 +- chopper/test/equatable_test.dart | 270 ++++++++++++++++++ .../test/fixtures/http_response_fixture.dart | 29 ++ chopper/test/fixtures/payload_fixture.dart | 19 ++ chopper/test/fixtures/request_fixture.dart | 36 +++ chopper/test/fixtures/response_fixture.dart | 29 ++ .../test/helpers/http_response_extension.dart | 21 ++ chopper/test/helpers/payload.dart | 27 ++ 11 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 chopper/test/equatable_test.dart create mode 100644 chopper/test/fixtures/http_response_fixture.dart create mode 100644 chopper/test/fixtures/payload_fixture.dart create mode 100644 chopper/test/fixtures/request_fixture.dart create mode 100644 chopper/test/fixtures/response_fixture.dart create mode 100644 chopper/test/helpers/http_response_extension.dart create mode 100644 chopper/test/helpers/payload.dart diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index e418f2c2..e4ecfdb6 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -2,11 +2,12 @@ import 'dart:async'; import 'package:chopper/src/extensions.dart'; import 'package:chopper/src/utils.dart'; +import 'package:equatable/equatable.dart' show EquatableMixin; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; /// This class represents an HTTP request that can be made with Chopper. -class Request extends http.BaseRequest { +class Request extends http.BaseRequest with EquatableMixin { final Uri uri; final Uri baseUri; final dynamic body; @@ -207,11 +208,25 @@ class Request extends http.BaseRequest { return request; } + + @override + List get props => [ + method, + uri, + baseUri, + body, + parameters, + headers, + multipart, + parts, + useBrackets, + includeNullQueryVars, + ]; } /// Represents a part in a multipart request. @immutable -class PartValue { +class PartValue with EquatableMixin { final T value; final String name; @@ -227,6 +242,12 @@ class PartValue { name ?? this.name, value ?? this.value as NewType, ); + + @override + List get props => [ + name, + value, + ]; } /// Represents a file part in a multipart request. diff --git a/chopper/lib/src/response.dart b/chopper/lib/src/response.dart index 1fc29fac..8f44e1c5 100644 --- a/chopper/lib/src/response.dart +++ b/chopper/lib/src/response.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; +import 'package:equatable/equatable.dart' show EquatableMixin; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; @@ -15,7 +16,7 @@ import 'package:meta/meta.dart'; /// Future> fetchItem(); /// ``` @immutable -class Response { +class Response with EquatableMixin { /// The [http.BaseResponse] from `package:http` that this [Response] wraps. final http.BaseResponse base; @@ -65,4 +66,11 @@ class Response { /// call was successful, else this will be `null`. String get bodyString => base is http.Response ? (base as http.Response).body : ''; + + @override + List get props => [ + base, + body, + error, + ]; } diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index 656c637c..c8853170 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'package:chopper/chopper.dart'; +import 'package:equatable/equatable.dart' show EquatableMixin; import 'package:logging/logging.dart'; /// Creates a new [Request] by copying [request] and adding a header with the @@ -130,7 +131,7 @@ Iterable<_Pair> _iterableToQuery( String _normalizeValue(value) => Uri.encodeComponent(value?.toString() ?? ''); -class _Pair { +class _Pair with EquatableMixin { final A first; final B second; final bool useBrackets; @@ -145,6 +146,12 @@ class _Pair { String toString() => useBrackets ? '$first${Uri.encodeQueryComponent('[]')}=$second' : '$first=$second'; + + @override + List get props => [ + first, + second, + ]; } bool isTypeOf() => _Instance() is _Instance; diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index ad1ac78f..815db4be 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -8,6 +8,7 @@ environment: sdk: ">=2.17.0 <3.0.0" dependencies: + equatable: ^2.0.5 http: ">=0.13.0 <1.0.0" logging: ^1.0.0 meta: ^1.3.0 @@ -17,7 +18,9 @@ dev_dependencies: build_test: ^2.0.0 collection: ^1.16.0 coverage: ^1.0.2 - dart_code_metrics: ^4.8.1 + dart_code_metrics: '>=4.8.1 <6.0.0' + data_fixture_dart: ^2.2.0 + faker: ^2.1.0 http_parser: ^4.0.0 lints: ^2.0.0 test: ^1.16.4 diff --git a/chopper/test/equatable_test.dart b/chopper/test/equatable_test.dart new file mode 100644 index 00000000..7bdebeae --- /dev/null +++ b/chopper/test/equatable_test.dart @@ -0,0 +1,270 @@ +import 'dart:convert' show jsonEncode; + +import 'package:chopper/chopper.dart'; +import 'package:faker/faker.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; + +import 'fixtures/http_response_fixture.dart' as http_fixture; +import 'fixtures/payload_fixture.dart'; +import 'fixtures/request_fixture.dart'; +import 'fixtures/response_fixture.dart'; +import 'helpers/payload.dart'; + +void main() { + final Faker faker = Faker(); + + group('Request', () { + final Uri baseUrl = Uri.parse(faker.internet.httpsUrl()); + late Request request; + + setUp(() { + request = RequestFixture.factory.makeSingle(); + }); + + test('should return true when comparing two identical objects', () { + expect( + Request( + 'GET', + Uri.parse('/foo'), + baseUrl, + headers: {'bar': 'baz'}, + ), + equals( + Request( + 'GET', + Uri.parse('/foo'), + baseUrl, + headers: {'bar': 'baz'}, + ), + ), + ); + }); + + test( + 'should return true when comparing original with copy', + () => expect( + request, + equals( + request.copyWith(), + ), + ), + ); + + test( + 'should return false when comparing two different objects', + () => expect( + request, + isNot( + equals( + RequestFixture.factory.makeSingle(), + ), + ), + ), + ); + + test( + 'should return false when comparing to null', + () => expect( + request, + isNot( + equals(null), + ), + ), + ); + + test( + 'should return false when comparing to an object of a different type', + () { + expect( + request, + isNot( + equals(faker.lorem.word()), + ), + ); + }, + ); + + test( + 'should return false when comparing to an object with different props', + () => expect( + request, + isNot( + equals( + request.copyWith( + headers: {'bar': 'bazzz'}, + ), + ), + ), + ), + ); + }); + + group('Response', () { + late Payload payload; + late Response response; + + setUp(() { + payload = PayloadFixture.factory.makeSingle(); + response = ResponseFixture.factory() + .redefine(ResponseFixture.factory().body(payload)) + .makeSingle(); + }); + + test('should return true when comparing two identical objects', () { + final http.Response base = http_fixture.ResponseFixture.factory + .redefine( + http_fixture.ResponseFixture.factory.body( + jsonEncode(payload), + ), + ) + .makeSingle(); + + expect( + Response(base, payload), + equals( + Response(base, payload), + ), + ); + }); + + test( + 'should return true when comparing original with copy', + () => expect( + response, + equals( + response.copyWith(), + ), + ), + ); + + test( + 'should return false when comparing two different objects', + () => expect( + response, + isNot( + equals( + ResponseFixture.factory() + .redefine(ResponseFixture.factory() + .body(PayloadFixture.factory.makeSingle())) + .makeSingle(), + ), + ), + ), + ); + + test( + 'should return false when comparing to null', + () => expect( + response, + isNot( + equals(null), + ), + ), + ); + + test( + 'should return false when comparing to an object of a different type', + () { + expect( + response, + isNot( + equals(faker.lorem.word()), + ), + ); + }, + ); + + test( + 'should return false when comparing to an object with different props', + () => expect( + response, + isNot( + equals( + response.copyWith( + body: PayloadFixture.factory.makeSingle(), + ), + ), + ), + ), + ); + }); + + group('PartValue', () { + late PartValue partValue; + + setUp(() { + partValue = PartValue( + faker.lorem.word(), + faker.lorem.word(), + ); + }); + + test('should return true when comparing two identical objects', () { + expect( + PartValue('foo', 'bar'), + equals( + PartValue('foo', 'bar'), + ), + ); + }); + + test( + 'should return true when comparing original with copy', + () => expect( + partValue, + equals( + partValue.copyWith(), + ), + ), + ); + + test( + 'should return false when comparing two different objects', + () => expect( + partValue, + isNot( + equals( + PartValue('bar', 'baz'), + ), + ), + ), + ); + + test( + 'should return false when comparing to null', + () => expect( + partValue, + isNot( + equals(null), + ), + ), + ); + + test( + 'should return false when comparing to an object of a different type', + () { + expect( + partValue, + isNot( + equals(faker.lorem.word()), + ), + ); + }, + ); + + test( + 'should return false when comparing to an object with different props', + () => expect( + partValue, + isNot( + equals( + partValue.copyWith( + value: 'bar', + ), + ), + ), + ), + ); + }); +} diff --git a/chopper/test/fixtures/http_response_fixture.dart b/chopper/test/fixtures/http_response_fixture.dart new file mode 100644 index 00000000..6b6b6071 --- /dev/null +++ b/chopper/test/fixtures/http_response_fixture.dart @@ -0,0 +1,29 @@ +import 'dart:convert' show jsonEncode; + +import 'package:data_fixture_dart/data_fixture_dart.dart'; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; + +import '../helpers/http_response_extension.dart'; +import 'payload_fixture.dart'; + +extension ResponseFixture on http.Response { + static ResponseFactory get factory => ResponseFactory(); +} + +@internal +class ResponseFactory extends FixtureFactory { + @override + FixtureDefinition definition() => define( + (Faker faker) => http.Response( + jsonEncode(PayloadFixture.factory.makeSingle().toJson()), + 200, + ), + ); + + FixtureRedefinitionBuilder body(String? body) => + (http.Response response) => response.copyWith(body: body); + + FixtureRedefinitionBuilder statusCode(int? statusCode) => + (http.Response response) => response.copyWith(statusCode: statusCode); +} diff --git a/chopper/test/fixtures/payload_fixture.dart b/chopper/test/fixtures/payload_fixture.dart new file mode 100644 index 00000000..435883cf --- /dev/null +++ b/chopper/test/fixtures/payload_fixture.dart @@ -0,0 +1,19 @@ +import 'package:data_fixture_dart/data_fixture_dart.dart'; +import 'package:meta/meta.dart'; + +import '../helpers/payload.dart'; + +extension PayloadFixture on Payload { + static PayloadFactory get factory => PayloadFactory(); +} + +@internal +class PayloadFactory extends FixtureFactory { + @override + FixtureDefinition definition() => define( + (Faker faker) => Payload( + statusCode: 200, + message: faker.lorem.sentence(), + ), + ); +} diff --git a/chopper/test/fixtures/request_fixture.dart b/chopper/test/fixtures/request_fixture.dart new file mode 100644 index 00000000..6d422cf4 --- /dev/null +++ b/chopper/test/fixtures/request_fixture.dart @@ -0,0 +1,36 @@ +import 'dart:convert' show jsonEncode; + +import 'package:chopper/chopper.dart' show Request; +import 'package:data_fixture_dart/data_fixture_dart.dart'; +import 'package:meta/meta.dart'; + +extension RequestFixture on Request { + static RequestFixtureFactory get factory => RequestFixtureFactory(); +} + +@internal +class RequestFixtureFactory extends FixtureFactory { + @override + FixtureDefinition definition() { + final String method = + faker.randomGenerator.element(['GET', 'POST', 'PUT', 'DELETE']); + + return define( + (Faker faker) => Request( + method, + Uri.parse('/${faker.lorem.word()}'), + Uri.https(faker.internet.domainName()), + headers: faker.randomGenerator.boolean() + ? {'x-${faker.lorem.word()}': faker.lorem.word()} + : {}, + parameters: faker.randomGenerator.boolean() + ? {faker.lorem.word(): faker.lorem.word()} + : null, + body: + faker.randomGenerator.boolean() && ['POST', 'PUT'].contains(method) + ? jsonEncode({faker.lorem.word(): faker.lorem.sentences(10)}) + : null, + ), + ); + } +} diff --git a/chopper/test/fixtures/response_fixture.dart b/chopper/test/fixtures/response_fixture.dart new file mode 100644 index 00000000..cd604e4f --- /dev/null +++ b/chopper/test/fixtures/response_fixture.dart @@ -0,0 +1,29 @@ +import 'package:chopper/chopper.dart' show Response; +import 'package:data_fixture_dart/data_fixture_dart.dart'; +import 'package:http/http.dart' as http; +import 'package:meta/meta.dart'; + +import 'http_response_fixture.dart' as http_fixture; + +extension ResponseFixture on Response { + static ResponseFixtureFactory factory() => ResponseFixtureFactory(); +} + +@internal +class ResponseFixtureFactory extends FixtureFactory> { + @override + FixtureDefinition> definition() { + final http.Response base = + http_fixture.ResponseFixture.factory.makeSingle(); + + return define( + (Faker faker) => Response(base, null), + ); + } + + FixtureRedefinitionBuilder> body(T? body) => + (Response response) => response.copyWith(body: body); + + FixtureRedefinitionBuilder> error(Object? value) => + (Response response) => response.copyWith(bodyError: value); +} diff --git a/chopper/test/helpers/http_response_extension.dart b/chopper/test/helpers/http_response_extension.dart new file mode 100644 index 00000000..17094acf --- /dev/null +++ b/chopper/test/helpers/http_response_extension.dart @@ -0,0 +1,21 @@ +import 'package:http/http.dart' as http; + +extension HttpResponseExtension on http.Response { + http.Response copyWith({ + String? body, + int? statusCode, + Map? headers, + bool? isRedirect, + bool? persistentConnection, + String? reasonPhrase, + }) => + http.Response( + body ?? this.body, + statusCode ?? this.statusCode, + request: request, + headers: headers ?? this.headers, + reasonPhrase: reasonPhrase ?? this.reasonPhrase, + isRedirect: isRedirect ?? this.isRedirect, + persistentConnection: persistentConnection ?? this.persistentConnection, + ); +} diff --git a/chopper/test/helpers/payload.dart b/chopper/test/helpers/payload.dart new file mode 100644 index 00000000..4c4ea8a3 --- /dev/null +++ b/chopper/test/helpers/payload.dart @@ -0,0 +1,27 @@ +import 'package:equatable/equatable.dart'; + +class Payload with EquatableMixin { + const Payload({ + this.statusCode = 200, + this.message = 'OK', + }); + + final int statusCode; + final String message; + + factory Payload.fromJson(Map json) => Payload( + statusCode: json['statusCode'] as int? ?? 200, + message: json['message'] as String? ?? 'OK', + ); + + Map toJson() => { + 'statusCode': statusCode, + 'message': message, + }; + + @override + List get props => [ + statusCode, + message, + ]; +} From 99767520e55704ac7d054a8deace4a950c63d629 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 13 Mar 2023 08:41:09 +0000 Subject: [PATCH 53/61] Add Feature request Github issue template (#414) --- .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..6532412f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. \ No newline at end of file From 617f3f399cee06c76f2e605139693e8f4d596494 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 29 Mar 2023 20:48:52 +0100 Subject: [PATCH 54/61] :construction_worker: add CODEOWNERS (#415) --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..1b115426 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @Guldem @JEuler @lejard-h @meysam1717 @pixeltoast @stewemetal @techouse \ No newline at end of file From d83ad405be18b3655bffedc09094aa80e76c27f6 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 12 May 2023 08:30:07 +0100 Subject: [PATCH 55/61] :arrow_up: Bump Dart SDK constraint to ">=2.17.0 <4.0.0" (#417) * :arrow_up: Bump Dart constraint to ">=2.17.0 <3.0.0" * :arrow_up: Bump mono_repo Yaml * :rotating_light: Ignore Element.enclosingElement3 warning --- .github/workflows/dart.yml | 98 ++++++++++--------- chopper/pubspec.yaml | 4 +- chopper_built_value/pubspec.yaml | 6 +- chopper_built_value/test/data.g.dart | 13 ++- chopper_built_value/test/serializers.g.dart | 4 +- chopper_generator/lib/src/generator.dart | 4 + chopper_generator/pubspec.yaml | 6 +- example/lib/built_value_resource.chopper.dart | 2 +- example/lib/built_value_resource.g.dart | 16 ++- example/lib/built_value_serializers.g.dart | 4 +- .../lib/json_decode_service.activator.g.dart | 2 +- example/lib/json_decode_service.vm.g.dart | 4 +- example/lib/json_decode_service.worker.g.dart | 12 +-- example/pubspec.yaml | 10 +- tool/ci.sh | 2 +- 15 files changed, 102 insertions(+), 85 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 25ae548e..8c57149d 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v6.4.2 +# Created with package:mono_repo v6.5.5 name: Dart CI on: push: @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable" @@ -30,22 +30,58 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - name: mono_repo self validate - run: dart pub global activate mono_repo 6.4.2 + run: dart pub global activate mono_repo 6.5.5 - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: + name: "analyze_and_format; PKG: chopper; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper + os:ubuntu-latest;pub-cache-hosted;sdk:stable + os:ubuntu-latest;pub-cache-hosted + os:ubuntu-latest + - name: Setup Dart SDK + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f + with: + sdk: stable + - id: checkout + name: Checkout repository + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - id: chopper_pub_upgrade + name: chopper; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: chopper + - name: "chopper; dart format --output=none --set-exit-if-changed ." + run: "dart format --output=none --set-exit-if-changed ." + if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" + working-directory: chopper + - name: "chopper; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" + working-directory: chopper + needs: + - job_001 + job_003: name: "analyzer_and_format; PKGS: chopper_built_value, chopper_generator; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper_built_value-chopper_generator;commands:format-analyze" @@ -55,12 +91,12 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade run: dart pub upgrade @@ -87,40 +123,6 @@ jobs: run: dart analyze --fatal-infos . if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - job_003: - name: "analyze_and_format; PKG: chopper; `dart format --output=none --set-exit-if-changed .`, `dart analyze --fatal-infos .`" - runs-on: ubuntu-latest - steps: - - name: Cache Pub hosted dependencies - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 - with: - path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" - restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper - os:ubuntu-latest;pub-cache-hosted;sdk:stable - os:ubuntu-latest;pub-cache-hosted - os:ubuntu-latest - - name: Setup Dart SDK - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d - with: - sdk: stable - - id: checkout - name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - - id: chopper_pub_upgrade - name: chopper; dart pub upgrade - run: dart pub upgrade - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: chopper - - name: "chopper; dart format --output=none --set-exit-if-changed ." - run: "dart format --output=none --set-exit-if-changed ." - if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" - working-directory: chopper - - name: "chopper; dart analyze --fatal-infos ." - run: dart analyze --fatal-infos . - if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" - working-directory: chopper needs: - job_001 - job_002 @@ -129,7 +131,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_1" @@ -139,12 +141,12 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - id: chopper_pub_upgrade name: chopper; dart pub upgrade run: dart pub upgrade @@ -172,7 +174,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies - uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_0" @@ -182,12 +184,12 @@ jobs: os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest - name: Setup Dart SDK - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d + uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable - id: checkout name: Checkout repository - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - id: chopper_pub_upgrade name: chopper; dart pub upgrade run: dart pub upgrade diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 815db4be..f6cfea6a 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,11 +1,11 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 6.0.0 +version: 6.1.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: equatable: ^2.0.5 diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 2275d2de..1f536048 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -1,11 +1,11 @@ name: chopper_built_value description: A built_value based Converter for Chopper. -version: 1.1.0 +version: 1.2.1 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: built_value: ^8.0.0 @@ -18,7 +18,7 @@ dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 built_value_generator: ^8.0.6 - dart_code_metrics: ^4.8.1 + dart_code_metrics: '>=4.8.1 <6.0.0' lints: ^2.0.0 dependency_overrides: diff --git a/chopper_built_value/test/data.g.dart b/chopper_built_value/test/data.g.dart index d9413768..82eb32ea 100644 --- a/chopper_built_value/test/data.g.dart +++ b/chopper_built_value/test/data.g.dart @@ -123,7 +123,11 @@ class _$DataModel extends DataModel { @override int get hashCode { - return $jf($jc($jc(0, id.hashCode), name.hashCode)); + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jf(_$hash); + return _$hash; } @override @@ -209,7 +213,10 @@ class _$ErrorModel extends ErrorModel { @override int get hashCode { - return $jf($jc(0, message.hashCode)); + var _$hash = 0; + _$hash = $jc(_$hash, message.hashCode); + _$hash = $jf(_$hash); + return _$hash; } @override @@ -261,4 +268,4 @@ class ErrorModelBuilder implements Builder { } } -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/chopper_built_value/test/serializers.g.dart b/chopper_built_value/test/serializers.g.dart index 55d2a7d3..8fc6a12d 100644 --- a/chopper_built_value/test/serializers.g.dart +++ b/chopper_built_value/test/serializers.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of serializers; +part of 'serializers.dart'; // ************************************************************************** // BuiltValueGenerator @@ -11,4 +11,4 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ErrorModel.serializer)) .build(); -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 67fb905f..cce54040 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -364,8 +364,12 @@ class ChopperGenerator extends GeneratorForAnnotation { }); } + /// TODO: Upgrade to `Element.enclosingElement` when analyzer 6.0.0 is released; in the mean time ignore the deprecation warning + /// https://github.com/dart-lang/sdk/blob/main/pkg/analyzer/CHANGELOG.md#520 String _factoryForFunction(FunctionTypedElement function) => + // ignore: deprecated_member_use function.enclosingElement3 is ClassElement + // ignore: deprecated_member_use ? '${function.enclosingElement3!.name}.${function.name}' : function.name!; diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index c4ed6cfe..716f5332 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,11 +1,11 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 6.0.0 +version: 6.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: analyzer: '>=4.4.0 <6.0.0' @@ -20,7 +20,7 @@ dependencies: dev_dependencies: test: ^1.16.4 - dart_code_metrics: ^4.8.1 + dart_code_metrics: '>=4.8.1 <6.0.0' lints: ^2.0.0 dependency_overrides: diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index 9b83b3fb..e30a468b 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of resource; +part of 'built_value_resource.dart'; // ************************************************************************** // ChopperGenerator diff --git a/example/lib/built_value_resource.g.dart b/example/lib/built_value_resource.g.dart index bc969e05..525a0594 100644 --- a/example/lib/built_value_resource.g.dart +++ b/example/lib/built_value_resource.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of resource; +part of 'built_value_resource.dart'; // ************************************************************************** // BuiltValueGenerator @@ -131,7 +131,11 @@ class _$Resource extends Resource { @override int get hashCode { - return $jf($jc($jc(0, id.hashCode), name.hashCode)); + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jf(_$hash); + return _$hash; } @override @@ -222,7 +226,11 @@ class _$ResourceError extends ResourceError { @override int get hashCode { - return $jf($jc($jc(0, type.hashCode), message.hashCode)); + var _$hash = 0; + _$hash = $jc(_$hash, type.hashCode); + _$hash = $jc(_$hash, message.hashCode); + _$hash = $jf(_$hash); + return _$hash; } @override @@ -284,4 +292,4 @@ class ResourceErrorBuilder } } -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/example/lib/built_value_serializers.g.dart b/example/lib/built_value_serializers.g.dart index 699b3f08..1dd64f7a 100644 --- a/example/lib/built_value_serializers.g.dart +++ b/example/lib/built_value_serializers.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of serializers; +part of 'built_value_serializers.dart'; // ************************************************************************** // BuiltValueGenerator @@ -11,4 +11,4 @@ Serializers _$serializers = (new Serializers().toBuilder() ..add(ResourceError.serializer)) .build(); -// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,deprecated_member_use_from_same_package,lines_longer_than_80_chars,no_leading_underscores_for_local_identifiers,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new,unnecessary_lambdas +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/example/lib/json_decode_service.activator.g.dart b/example/lib/json_decode_service.activator.g.dart index 1756d3d3..1e151e3c 100644 --- a/example/lib/json_decode_service.activator.g.dart +++ b/example/lib/json_decode_service.activator.g.dart @@ -1,7 +1,7 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** -// SquadronWorkerGenerator +// Generated by: WorkerGenerator // ************************************************************************** import 'json_decode_service.vm.g.dart'; diff --git a/example/lib/json_decode_service.vm.g.dart b/example/lib/json_decode_service.vm.g.dart index 7ad9a226..e798c22e 100644 --- a/example/lib/json_decode_service.vm.g.dart +++ b/example/lib/json_decode_service.vm.g.dart @@ -1,13 +1,13 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** -// SquadronWorkerGenerator +// Generated by: WorkerGenerator // ************************************************************************** import 'package:squadron/squadron_service.dart'; import 'json_decode_service.dart'; // VM entry point -void _start(Map command) => run($JsonDecodeServiceInitializer, command); +void _start(Map command) => run($JsonDecodeServiceInitializer, command, null); dynamic $getJsonDecodeServiceActivator() => _start; diff --git a/example/lib/json_decode_service.worker.g.dart b/example/lib/json_decode_service.worker.g.dart index d50372ab..0119d302 100644 --- a/example/lib/json_decode_service.worker.g.dart +++ b/example/lib/json_decode_service.worker.g.dart @@ -3,7 +3,7 @@ part of 'json_decode_service.dart'; // ************************************************************************** -// SquadronWorkerGenerator +// WorkerGenerator // ************************************************************************** // Operations map for JsonDecodeService @@ -14,9 +14,8 @@ mixin $JsonDecodeServiceOperations on WorkerService { static const int _$jsonDecodeId = 1; - static Map _getOperations(JsonDecodeService svc) => { - _$jsonDecodeId: (r) => svc.jsonDecode(r.args[0]), - }; + static Map _getOperations(JsonDecodeService svc) => + {_$jsonDecodeId: (req) => svc.jsonDecode(req.args[0])}; } // Service initializer @@ -33,9 +32,6 @@ class JsonDecodeServiceWorker extends Worker Future jsonDecode(String source) => send( $JsonDecodeServiceOperations._$jsonDecodeId, args: [source], - token: null, - inspectRequest: false, - inspectResponse: false, ); @override @@ -52,7 +48,7 @@ class JsonDecodeServiceWorkerPool extends WorkerPool @override Future jsonDecode(String source) => - execute((w) => w.jsonDecode(source)); + execute(($w) => $w.jsonDecode(source)); @override Map get operations => WorkerService.noOperations; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 47051983..963f23c9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,11 +1,11 @@ name: chopper_example description: Example usage of the Chopper package -version: 0.0.3 +version: 0.0.4 documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard environment: - sdk: '>=2.17.0 <3.0.0' + sdk: '>=2.17.0 <4.0.0' dependencies: chopper: @@ -14,16 +14,16 @@ dependencies: analyzer: http: built_collection: - squadron: ^4.3.0 + squadron: ^4.3.8 dev_dependencies: build_runner: chopper_generator: json_serializable: built_value_generator: - dart_code_metrics: ^4.8.1 + dart_code_metrics: '>=4.8.1 <6.0.0' lints: ^2.0.0 - squadron_builder: ^0.9.0 + squadron_builder: ^2.0.0 dependency_overrides: chopper: diff --git a/tool/ci.sh b/tool/ci.sh index 372d5024..ca07f3a6 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Created with package:mono_repo v6.4.2 +# Created with package:mono_repo v6.5.5 # Support built in commands on windows out of the box. # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") From 83f8c259a9eec9e51b87fc0f3579d200ecaabc34 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 13 May 2023 19:03:02 +0100 Subject: [PATCH 56/61] :green_heart: Fix CI publish (#419) --- .github/workflows/publish.yml | 63 ++++++++++++++++---------- .github/workflows/publish_dry_run.yml | 47 +++++++++++++++++++ chopper/.DS_Store | Bin 6148 -> 0 bytes chopper/CHANGELOG.md | 12 +++++ chopper_built_value/CHANGELOG.md | 8 ++++ chopper_generator/CHANGELOG.md | 4 ++ tool/publish.sh | 11 +++-- 7 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/publish_dry_run.yml delete mode 100644 chopper/.DS_Store diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index aea02d47..0eab9e8e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,45 +2,58 @@ name: Publish packages on: push: - branches: ['master'] + branches: + - master +defaults: + run: + shell: bash +env: + PUB_ENVIRONMENT: bot.github +permissions: read-all jobs: publish_chopper: - name: "Publish chopper" - runs-on: ubuntu-latest - steps: - - uses: dart-lang/setup-dart@v1.3 + name: "Publish chopper" + runs-on: ubuntu-latest + steps: + - uses: dart-lang/setup-dart@v1 with: sdk: stable - id: checkout uses: actions/checkout@v3 + - id: credentials + run: | + mkdir -p $XDG_CONFIG_HOME/dart + echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - id: publish run: bash tool/publish.sh chopper - env: - CREDENTIAL_JSON: ${{ secrets.CREDENTIAL_JSON }} publish_chopper_generator: name: "Publish chopper_generator" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.3 - with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: publish - run: bash tool/publish.sh chopper_generator - env: - CREDENTIAL_JSON: ${{ secrets.CREDENTIAL_JSON }} + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v3 + - id: credentials + run: | + mkdir -p $XDG_CONFIG_HOME/dart + echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + - id: publish + run: bash tool/publish.sh chopper_generator publish_chopper_built_value: name: "Publish chopper_built_value" runs-on: ubuntu-latest steps: - - uses: dart-lang/setup-dart@v1.3 - with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: publish - run: bash tool/publish.sh chopper_built_value - env: - CREDENTIAL_JSON: ${{ secrets.CREDENTIAL_JSON }} \ No newline at end of file + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v3 + - id: credentials + run: | + mkdir -p $XDG_CONFIG_HOME/dart + echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + - id: publish + run: bash tool/publish.sh chopper_built_value \ No newline at end of file diff --git a/.github/workflows/publish_dry_run.yml b/.github/workflows/publish_dry_run.yml new file mode 100644 index 00000000..4e7fca02 --- /dev/null +++ b/.github/workflows/publish_dry_run.yml @@ -0,0 +1,47 @@ +name: Publish packages (dry run) + +on: + pull_request: + branches: + - master +defaults: + run: + shell: bash +env: + PUB_ENVIRONMENT: bot.github +permissions: read-all + +jobs: + publish_chopper: + name: "Publish chopper (dry run)" + runs-on: ubuntu-latest + steps: + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v3 + - id: publish_dry_run + run: bash tool/publish.sh chopper --dry-run + publish_chopper_generator: + name: "Publish chopper_generator (dry run)" + runs-on: ubuntu-latest + steps: + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v3 + - id: publish_dry_run + run: bash tool/publish.sh chopper_generator --dry-run + publish_chopper_built_value: + name: "Publish chopper_built_value (dry run)" + runs-on: ubuntu-latest + steps: + - uses: dart-lang/setup-dart@v1 + with: + sdk: stable + - id: checkout + uses: actions/checkout@v3 + - id: publish_dry_run + run: bash tool/publish.sh chopper_built_value --dry-run \ No newline at end of file diff --git a/chopper/.DS_Store b/chopper/.DS_Store deleted file mode 100644 index e8a2b199799712c46a4fa61772f920ea4f1144d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5dPK{+F}ny5IyGTso>d5S{t){ES`W4GT=EeayD3 zb>J^DAbWR=0iLM|vimp3l6yJh%mPi$4Df`co6LrBGK=XYugnGY?Ar=^KpmI3Mhkbi zLWCRK6PxrK75qL6en%KG(i&sDFwTV7N6g;H_M6OLjY)nD^Ilk^La$ew<(!p_^VT-g zk4&Z|+Ds7_tR%0jRfRQlSyRM{dWiDY=A>X<#~dr>y&#TR(H_>!SHZ7_2S#Y2gInS* zzR0twX0k7HZ^n$X%x47VpR;2i6=gsfPzH7|z%yHl#L#2vkRufR5wJ99qYV5i13&D&iRl0U diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index 32f0af43..8efd695f 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,4 +1,16 @@ # Changelog + +## 6.1.2 +- Packages upgrade, constraints upgrade + +## 6.1.1 +- EquatableMixin for Request, Response and PartValue + +## 6.1.0 + +- HttpLogging interceptor more configurable +- Apply headers field name case insensitive. + ## 6.0.0 - Replaced the String based path with Uri (BREAKING CHANGE) diff --git a/chopper_built_value/CHANGELOG.md b/chopper_built_value/CHANGELOG.md index e3a476bc..c78c9e07 100644 --- a/chopper_built_value/CHANGELOG.md +++ b/chopper_built_value/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.2.1 + +- Packages upgrade, constraints upgrade + +## 1.2.0 + +- Chopper upgraded + ## 1.1.0 - Chopper upgraded diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 39fc57cd..4fa0cf97 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 6.0.1 + +- Packages upgrade, constraints upgrade + ## 6.0.0 - Replaced the String based path with Uri (BREAKING CHANGE) diff --git a/tool/publish.sh b/tool/publish.sh index 26340f52..f5b81db2 100644 --- a/tool/publish.sh +++ b/tool/publish.sh @@ -6,13 +6,14 @@ PKG=$1 echo -e "\033[1mPKG: ${PKG}\033[22m" pushd "${PKG}" -mkdir -p ~/.pub-cache - -echo $CREDENTIAL_JSON > ~/.pub-cache/credentials.json - sed '/Comment before publish$/,+2 d' pubspec.yaml > pubspec.temp.yaml rm pubspec.yaml mv pubspec.temp.yaml pubspec.yaml -dart pub publish -f +if [ "$2" == "--dry-run" ]; then + dart pub publish --dry-run +else + dart pub publish --force +fi + popd \ No newline at end of file From 64bd220ed52c6fcad01344318f8185a5ee5cd68a Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 14 May 2023 07:38:48 +0100 Subject: [PATCH 57/61] :green_heart: Fix CI publish credentials (#421) --- .github/workflows/publish.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0eab9e8e..1ed7dbc0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -24,7 +24,7 @@ jobs: - id: credentials run: | mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - id: publish run: bash tool/publish.sh chopper publish_chopper_generator: @@ -39,7 +39,7 @@ jobs: - id: credentials run: | mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - id: publish run: bash tool/publish.sh chopper_generator publish_chopper_built_value: @@ -54,6 +54,6 @@ jobs: - id: credentials run: | mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.PUB_CREDENTIALS }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - id: publish run: bash tool/publish.sh chopper_built_value \ No newline at end of file From 09774f1524f10069cd8dc91fbbbd6273bbaf0bea Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 14 May 2023 17:51:36 +0100 Subject: [PATCH 58/61] :green_heart: Fix CI coverage reporting (#423) --- .github/workflows/dart.yml | 59 ++++++++++++++----------------- chopper/mono_pkg.yaml | 2 +- chopper_built_value/mono_pkg.yaml | 2 +- mono_repo.yaml | 30 +++++----------- tool/ci.sh | 10 +++--- tool/coverage.sh | 11 ------ 6 files changed, 42 insertions(+), 72 deletions(-) delete mode 100755 tool/coverage.sh diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 8c57149d..444801b6 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -127,14 +127,14 @@ jobs: - job_001 - job_002 job_004: - name: "unit_test; PKGS: chopper, chopper_built_value; `dart test -p chrome`" + name: "unit_test; PKGS: chopper, chopper_built_value; `dart pub global run coverage:test_with_coverage`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_1" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_with_coverage" restore-keys: | os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value os:ubuntu-latest;pub-cache-hosted;sdk:stable @@ -144,6 +144,8 @@ jobs: uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f with: sdk: stable + - name: "Activate package:coverage" + run: "dart pub global activate coverage '>=1.5.0'" - id: checkout name: Checkout repository uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab @@ -152,32 +154,44 @@ jobs: run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - - name: "chopper; dart test -p chrome" - run: dart test -p chrome + - name: "chopper; dart pub global run coverage:test_with_coverage" + run: "dart pub global run coverage:test_with_coverage" if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@main + with: + files: chopper/coverage/lcov.info + fail_ci_if_error: true + name: coverage_00 - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - - name: "chopper_built_value; dart test -p chrome" - run: dart test -p chrome + - name: "chopper_built_value; dart pub global run coverage:test_with_coverage" + run: "dart pub global run coverage:test_with_coverage" if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@main + with: + files: chopper_built_value/coverage/lcov.info + fail_ci_if_error: true + name: coverage_01 needs: - job_001 - job_002 - job_003 job_005: - name: "unit_test; PKGS: chopper, chopper_built_value; `dart test`" + name: "unit_test; PKGS: chopper, chopper_built_value; `dart test -p chrome`" runs-on: ubuntu-latest steps: - name: Cache Pub hosted dependencies uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test_0" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value;commands:test" restore-keys: | os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value os:ubuntu-latest;pub-cache-hosted;sdk:stable @@ -195,8 +209,8 @@ jobs: run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper - - name: chopper; dart test - run: dart test + - name: "chopper; dart test -p chrome" + run: dart test -p chrome if: "always() && steps.chopper_pub_upgrade.conclusion == 'success'" working-directory: chopper - id: chopper_built_value_pub_upgrade @@ -204,32 +218,11 @@ jobs: run: dart pub upgrade if: "always() && steps.checkout.conclusion == 'success'" working-directory: chopper_built_value - - name: chopper_built_value; dart test - run: dart test + - name: "chopper_built_value; dart test -p chrome" + run: dart test -p chrome if: "always() && steps.chopper_built_value_pub_upgrade.conclusion == 'success'" working-directory: chopper_built_value needs: - job_001 - job_002 - job_003 - job_006: - name: Coverage - runs-on: ubuntu-latest - steps: - - uses: dart-lang/setup-dart@v1.3 - with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: upload_coverage - name: chopper; tool/coverage.sh - run: bash tool/coverage.sh - if: "always() && steps.checkout.conclusion == 'success'" - env: - CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" - needs: - - job_001 - - job_002 - - job_003 - - job_004 - - job_005 diff --git a/chopper/mono_pkg.yaml b/chopper/mono_pkg.yaml index ed853726..8cce50b9 100644 --- a/chopper/mono_pkg.yaml +++ b/chopper/mono_pkg.yaml @@ -7,7 +7,7 @@ stages: - format - analyze: --fatal-infos . - unit_test: - - test: + - test_with_coverage: - test: -p chrome cache: diff --git a/chopper_built_value/mono_pkg.yaml b/chopper_built_value/mono_pkg.yaml index 3d4d539a..ae7b5f25 100644 --- a/chopper_built_value/mono_pkg.yaml +++ b/chopper_built_value/mono_pkg.yaml @@ -7,7 +7,7 @@ stages: - format - analyze: --fatal-infos . - unit_test: - - test: + - test_with_coverage: - test: -p chrome cache: diff --git a/mono_repo.yaml b/mono_repo.yaml index 08251824..28e29828 100644 --- a/mono_repo.yaml +++ b/mono_repo.yaml @@ -4,28 +4,16 @@ github: on: push: branches: - - master - - develop + - master + - develop pull_request: branches: - - master - - develop - on_completion: - - name: "Coverage" - runs-on: ubuntu-latest - steps: - - uses: dart-lang/setup-dart@v1.3 - with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: upload_coverage - name: "chopper; tool/coverage.sh" - if: "always() && steps.checkout.conclusion == 'success'" - run: bash tool/coverage.sh - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - master + - develop merge_stages: -- analyzer_and_format -- unit_test \ No newline at end of file + - analyzer_and_format + - unit_test + +coverage_service: + - codecov \ No newline at end of file diff --git a/tool/ci.sh b/tool/ci.sh index ca07f3a6..7449806d 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -75,14 +75,14 @@ for PKG in ${PKGS}; do echo 'dart format --output=none --set-exit-if-changed .' dart format --output=none --set-exit-if-changed . || EXIT_CODE=$? ;; - test_0) - echo 'dart test' - dart test || EXIT_CODE=$? - ;; - test_1) + test) echo 'dart test -p chrome' dart test -p chrome || EXIT_CODE=$? ;; + test_with_coverage) + echo 'dart pub global run coverage:test_with_coverage' + dart pub global run coverage:test_with_coverage || EXIT_CODE=$? + ;; *) echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m" exit 64 diff --git a/tool/coverage.sh b/tool/coverage.sh deleted file mode 100755 index 8fb7f288..00000000 --- a/tool/coverage.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -dart pub get -dart run test --coverage=coverage -dart run coverage:format_coverage --lcov \ - --in=coverage \ - --out=coverage/coverage.lcov \ - --packages=.packages \ - --report-on=lib - -curl -s https://codecov.io/bash | bash \ No newline at end of file From 01450641b67d16dc4654e87e55b13dd7686f23a4 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 15 May 2023 06:18:19 +0100 Subject: [PATCH 59/61] :construction_worker: only run CI publish / --dry-run when a package's version changes (#424) --- .github/workflows/publish.yml | 77 +++++++++++++++++---------- .github/workflows/publish_dry_run.yml | 64 +++++++++++++++------- tool/compare_versions.dart | 38 +++++++++++++ tool/pubspec.yaml | 12 +++++ 4 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 tool/compare_versions.dart create mode 100644 tool/pubspec.yaml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1ed7dbc0..5d582264 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,7 @@ name: Publish packages on: - push: + pull_request: branches: - master defaults: @@ -12,48 +12,71 @@ env: permissions: read-all jobs: - publish_chopper: - name: "Publish chopper" + get_base_version: + name: "Get base version" runs-on: ubuntu-latest + strategy: + matrix: + package: [ chopper, chopper_generator, chopper_built_value ] + outputs: + BASE_VERSION_chopper: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper }} + BASE_VERSION_chopper_generator: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper_generator }} + BASE_VERSION_chopper_built_value: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper_built_value }} steps: - uses: dart-lang/setup-dart@v1 with: sdk: stable - id: checkout uses: actions/checkout@v3 - - id: credentials - run: | - mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - - id: publish - run: bash tool/publish.sh chopper - publish_chopper_generator: - name: "Publish chopper_generator" - runs-on: ubuntu-latest - steps: - - uses: dart-lang/setup-dart@v1 with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: credentials + ref: ${{ github.event.pull_request.base.ref }} + - name: Load base version + id: load_base_version run: | - mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - - id: publish - run: bash tool/publish.sh chopper_generator - publish_chopper_built_value: - name: "Publish chopper_built_value" + set -e + echo "BASE_VERSION_${{ matrix.package }}=$(awk '/^version: / {print $2}' ${{ matrix.package }}/pubspec.yaml)" >> $GITHUB_OUTPUT + publish: + name: "Publish" + needs: get_base_version runs-on: ubuntu-latest + strategy: + matrix: + package: [ chopper, chopper_generator, chopper_built_value ] steps: - uses: dart-lang/setup-dart@v1 with: sdk: stable - id: checkout uses: actions/checkout@v3 - - id: credentials + - name: Load this version + id: load_this_version + run: | + set -e + echo "THIS_VERSION=$(awk '/^version: / {print $2}' ${{ matrix.package }}/pubspec.yaml)" >> $GITHUB_ENV + - name: Compare versions + id: compare_versions + env: + BASE_VERSION_chopper: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper }} + BASE_VERSION_chopper_generator: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper_generator }} + BASE_VERSION_chopper_built_value: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper_built_value }} + run: | + set -e + pushd tool || exit + dart pub get + echo "IS_VERSION_GREATER=$(dart run compare_versions.dart $THIS_VERSION $BASE_VERSION_${{ matrix.package }})" >> $GITHUB_ENV + popd || exit + - name: Set up pub credentials + id: credentials + if: ${{ env.IS_VERSION_GREATER == 1 }} run: | + set -e mkdir -p $XDG_CONFIG_HOME/dart echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" - - id: publish - run: bash tool/publish.sh chopper_built_value \ No newline at end of file + - name: Publish + id: publish + if: ${{ env.IS_VERSION_GREATER == 1 }} + run: bash tool/publish.sh ${{ matrix.package }} + - name: Skip publish + id: skip_publish + if: ${{ env.IS_VERSION_GREATER == 0 }} + run: echo "Skipping publish for ${{ matrix.package }} because the version is not greater than the one on pub.dev" \ No newline at end of file diff --git a/.github/workflows/publish_dry_run.yml b/.github/workflows/publish_dry_run.yml index 4e7fca02..b16ef582 100644 --- a/.github/workflows/publish_dry_run.yml +++ b/.github/workflows/publish_dry_run.yml @@ -12,36 +12,64 @@ env: permissions: read-all jobs: - publish_chopper: - name: "Publish chopper (dry run)" + get_base_version: + name: "Get base version" runs-on: ubuntu-latest + strategy: + matrix: + package: [ chopper, chopper_generator, chopper_built_value ] + outputs: + BASE_VERSION_chopper: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper }} + BASE_VERSION_chopper_generator: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper_generator }} + BASE_VERSION_chopper_built_value: ${{ steps.load_base_version.outputs.BASE_VERSION_chopper_built_value }} steps: - uses: dart-lang/setup-dart@v1 with: sdk: stable - id: checkout uses: actions/checkout@v3 - - id: publish_dry_run - run: bash tool/publish.sh chopper --dry-run - publish_chopper_generator: - name: "Publish chopper_generator (dry run)" - runs-on: ubuntu-latest - steps: - - uses: dart-lang/setup-dart@v1 with: - sdk: stable - - id: checkout - uses: actions/checkout@v3 - - id: publish_dry_run - run: bash tool/publish.sh chopper_generator --dry-run - publish_chopper_built_value: - name: "Publish chopper_built_value (dry run)" + ref: ${{ github.event.pull_request.base.ref }} + - name: Load base version + id: load_base_version + run: | + set -e + echo "BASE_VERSION_${{ matrix.package }}=$(awk '/^version: / {print $2}' ${{ matrix.package }}/pubspec.yaml)" >> $GITHUB_OUTPUT + publish_dry_run: + name: "Publish DRY RUN" + needs: get_base_version runs-on: ubuntu-latest + strategy: + matrix: + package: [ chopper, chopper_generator, chopper_built_value ] steps: - uses: dart-lang/setup-dart@v1 with: sdk: stable - id: checkout uses: actions/checkout@v3 - - id: publish_dry_run - run: bash tool/publish.sh chopper_built_value --dry-run \ No newline at end of file + - name: Load this version + id: load_this_version + run: | + set -e + echo "THIS_VERSION=$(awk '/^version: / {print $2}' ${{ matrix.package }}/pubspec.yaml)" >> $GITHUB_ENV + - name: Compare versions + id: compare_versions + env: + BASE_VERSION_chopper: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper }} + BASE_VERSION_chopper_generator: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper_generator }} + BASE_VERSION_chopper_built_value: ${{ needs.get_base_version.outputs.BASE_VERSION_chopper_built_value }} + run: | + set -e + pushd tool || exit + dart pub get + echo "IS_VERSION_GREATER=$(dart run compare_versions.dart $THIS_VERSION $BASE_VERSION_${{ matrix.package }})" >> $GITHUB_ENV + popd || exit + - name: Publish (dry run) + id: publish_dry_run + if: ${{ env.IS_VERSION_GREATER == 1 }} + run: bash tool/publish.sh ${{ matrix.package }} --dry-run + - name: Skip publish (dry run) + id: skip_publish_dry_run + if: ${{ env.IS_VERSION_GREATER == 0 }} + run: echo "Skipping publish (dry run) for ${{ matrix.package }} because the version is not greater than the one on pub.dev" \ No newline at end of file diff --git a/tool/compare_versions.dart b/tool/compare_versions.dart new file mode 100644 index 00000000..1f4d03c8 --- /dev/null +++ b/tool/compare_versions.dart @@ -0,0 +1,38 @@ +import 'dart:io' show exitCode, stderr, stdout; +import 'package:cli_script/cli_script.dart' show wrapMain; +import 'package:pub_semver/pub_semver.dart' show Version; + +void main(List args) { + wrapMain(() { + exitCode = 0; + + if (args.length != 2) { + stderr.write( + 'Please provide two arguments!\n\nExample usage:\ndart run compare_versions.dart 2.0.0+1 1.9.0+5\n', + ); + exitCode = 1; + return; + } + + late final Version v1; + late final Version v2; + + try { + v1 = Version.parse(args[0]); + } on FormatException catch (e) { + stderr.write('Error parsing version 1: ${e.message}'); + exitCode = 1; + return; + } + + try { + v2 = Version.parse(args[1]); + } on FormatException catch (e) { + stderr.write('Error parsing version 2: ${e.message}'); + exitCode = 1; + return; + } + + stdout.write(v1 > v2 ? 1 : 0); + }); +} diff --git a/tool/pubspec.yaml b/tool/pubspec.yaml new file mode 100644 index 00000000..5ad9b46a --- /dev/null +++ b/tool/pubspec.yaml @@ -0,0 +1,12 @@ +name: compare_versions + +publish_to: 'none' + +version: 1.0.0 + +environment: + sdk: ">=2.17.0 <4.0.0" + +dependencies: + cli_script: ^0.3.1 + pub_semver: ^2.1.4 \ No newline at end of file From ef2fbf59dca8e79eb39c13f16743c66aef4f4c9e Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 15 May 2023 06:48:14 +0100 Subject: [PATCH 60/61] :green_heart: fix publish CI to run only on push (#425) --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5d582264..8591699a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,7 +1,7 @@ name: Publish packages on: - pull_request: + push: branches: - master defaults: From 08781c999565ec5f1e562f74c47fcdb0acd256e2 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 20 May 2023 12:52:00 +0100 Subject: [PATCH 61/61] :green_heart: fix publish workflow (#427) * :green_heart: fix publish workflow * :rotating_light: silence deprecation warning --- .github/workflows/publish.yml | 3 ++- chopper_generator/lib/src/generator.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8591699a..e29d5e21 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,8 @@ jobs: - id: checkout uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 2 + - run: git checkout HEAD^ - name: Load base version id: load_base_version run: | diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index cce54040..72635a75 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -457,6 +457,7 @@ class ChopperGenerator extends GeneratorForAnnotation { _typeChecker(Map).isExactlyType(type) || _typeChecker(BuiltMap).isExactlyType(type)) return type; + // ignore: deprecated_member_use if (generic.isDynamic) return null; if (_typeChecker(List).isExactlyType(type) ||