From f25fbe4a9e2eab08424f48030d271681a37ca2ba Mon Sep 17 00:00:00 2001 From: Youssef Raafat Date: Tue, 25 May 2021 08:26:19 +0200 Subject: [PATCH 001/102] 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 002/102] 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 003/102] 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 004/102] 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 005/102] 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 006/102] 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 007/102] 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 008/102] 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 009/102] 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 010/102] 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 011/102] 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 012/102] 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 013/102] 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 014/102] 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 015/102] 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 016/102] 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 017/102] 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 018/102] 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 019/102] 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 020/102] 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 021/102] 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 022/102] 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 023/102] 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 024/102] 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 025/102] 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 026/102] [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 027/102] 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 028/102] 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 029/102] [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 030/102] 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 031/102] 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 032/102] 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 033/102] 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 034/102] 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 035/102] 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 036/102] [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 037/102] 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 038/102] 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 039/102] 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 040/102] [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 041/102] :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 042/102] 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 043/102] 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 044/102] 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 045/102] 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 046/102] 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 047/102] 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 048/102] 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 049/102] :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 050/102] 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 051/102] 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 052/102] #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 053/102] 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 054/102] :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 055/102] :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 056/102] :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 057/102] :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 058/102] :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 059/102] :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 060/102] :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 061/102] :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) || From 485ef8fdfd04638e4ab617724d00ca606857f90a Mon Sep 17 00:00:00 2001 From: Joran Dob Date: Fri, 26 May 2023 11:23:22 +0200 Subject: [PATCH 062/102] Adding follow redirects to toHttpRequest (#430) --- chopper/lib/src/request.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index e4ecfdb6..7e0b21f2 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -138,6 +138,7 @@ class Request extends http.BaseRequest with EquatableMixin { @visibleForTesting http.Request toHttpRequest() { final http.Request request = http.Request(method, url) + ..followRedirects = followRedirects ..headers.addAll(headers); if (body != null) { From a88aa3a495009261c0abd01b0fe4b22efd26d66a Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 27 May 2023 09:03:18 +0100 Subject: [PATCH 063/102] :arrow_up: update squadron example (#432) --- example/build.yaml | 6 +- example/lib/json_decode_service.vm.g.dart | 2 +- example/lib/json_decode_service.worker.g.dart | 226 +++++++++++++++++- example/pubspec.yaml | 4 +- 4 files changed, 230 insertions(+), 8 deletions(-) diff --git a/example/build.yaml b/example/build.yaml index d0fb37db..b8099626 100644 --- a/example/build.yaml +++ b/example/build.yaml @@ -13,4 +13,8 @@ targets: any_map: false checked: false explicit_to_json: true - create_to_json: true \ No newline at end of file + create_to_json: true + squadron_builder:worker_builder: + options: + with_finalizers: true + serialization_type: List \ No newline at end of file diff --git a/example/lib/json_decode_service.vm.g.dart b/example/lib/json_decode_service.vm.g.dart index e798c22e..656bcef1 100644 --- a/example/lib/json_decode_service.vm.g.dart +++ b/example/lib/json_decode_service.vm.g.dart @@ -8,6 +8,6 @@ import 'package:squadron/squadron_service.dart'; import 'json_decode_service.dart'; // VM entry point -void _start(Map command) => run($JsonDecodeServiceInitializer, command, null); +void _start(List 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 0119d302..0cd306ce 100644 --- a/example/lib/json_decode_service.worker.g.dart +++ b/example/lib/json_decode_service.worker.g.dart @@ -23,10 +23,10 @@ JsonDecodeService $JsonDecodeServiceInitializer(WorkerRequest startRequest) => JsonDecodeService(); // Worker for JsonDecodeService -class JsonDecodeServiceWorker extends Worker +class _JsonDecodeServiceWorker extends Worker with $JsonDecodeServiceOperations implements JsonDecodeService { - JsonDecodeServiceWorker() : super($JsonDecodeServiceActivator); + _JsonDecodeServiceWorker() : super($JsonDecodeServiceActivator); @override Future jsonDecode(String source) => send( @@ -36,13 +36,109 @@ class JsonDecodeServiceWorker extends Worker @override Map get operations => WorkerService.noOperations; + + final Object _detachToken = Object(); +} + +// Finalizable worker wrapper for JsonDecodeService +class JsonDecodeServiceWorker implements _JsonDecodeServiceWorker { + JsonDecodeServiceWorker() : _worker = _JsonDecodeServiceWorker() { + _finalizer.attach(this, _worker, detach: _worker._detachToken); + } + + final _JsonDecodeServiceWorker _worker; + + static final Finalizer<_JsonDecodeServiceWorker> _finalizer = + Finalizer<_JsonDecodeServiceWorker>((w) { + try { + _finalizer.detach(w._detachToken); + w.stop(); + } catch (ex) { + // finalizers must not throw + } + }); + + @override + Future jsonDecode(String source) => _worker.jsonDecode(source); + + @override + Map get operations => _worker.operations; + + @override + List get args => _worker.args; + + @override + Channel? get channel => _worker.channel; + + @override + Duration get idleTime => _worker.idleTime; + + @override + bool get isStopped => _worker.isStopped; + + @override + int get maxWorkload => _worker.maxWorkload; + + @override + WorkerStat get stats => _worker.stats; + + @override + String get status => _worker.status; + + @override + int get totalErrors => _worker.totalErrors; + + @override + int get totalWorkload => _worker.totalWorkload; + + @override + Duration get upTime => _worker.upTime; + + @override + String get workerId => _worker.workerId; + + @override + int get workload => _worker.workload; + + @override + Future start() => _worker.start(); + + @override + void stop() => _worker.stop(); + + @override + Future send(int command, + {List args = const [], + CancellationToken? token, + bool inspectRequest = false, + bool inspectResponse = false}) => + _worker.send(command, + args: args, + token: token, + inspectRequest: inspectRequest, + inspectResponse: inspectResponse); + + @override + Stream stream(int command, + {List args = const [], + CancellationToken? token, + bool inspectRequest = false, + bool inspectResponse = false}) => + _worker.stream(command, + args: args, + token: token, + inspectRequest: inspectRequest, + inspectResponse: inspectResponse); + + @override + Object get _detachToken => _worker._detachToken; } // Worker pool for JsonDecodeService -class JsonDecodeServiceWorkerPool extends WorkerPool +class _JsonDecodeServiceWorkerPool extends WorkerPool with $JsonDecodeServiceOperations implements JsonDecodeService { - JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) + _JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) : super(() => JsonDecodeServiceWorker(), concurrencySettings: concurrencySettings); @@ -52,4 +148,126 @@ class JsonDecodeServiceWorkerPool extends WorkerPool @override Map get operations => WorkerService.noOperations; + + final Object _detachToken = Object(); +} + +// Finalizable worker pool wrapper for JsonDecodeService +class JsonDecodeServiceWorkerPool implements _JsonDecodeServiceWorkerPool { + JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) + : _pool = _JsonDecodeServiceWorkerPool( + concurrencySettings: concurrencySettings) { + _finalizer.attach(this, _pool, detach: _pool._detachToken); + } + + final _JsonDecodeServiceWorkerPool _pool; + + static final Finalizer<_JsonDecodeServiceWorkerPool> _finalizer = + Finalizer<_JsonDecodeServiceWorkerPool>((p) { + try { + _finalizer.detach(p._detachToken); + p.stop(); + } catch (ex) { + // finalizers must not throw + } + }); + + @override + Future jsonDecode(String source) => _pool.jsonDecode(source); + + @override + Map get operations => _pool.operations; + + @override + ConcurrencySettings get concurrencySettings => _pool.concurrencySettings; + + @override + Iterable get fullStats => _pool.fullStats; + + @override + int get maxConcurrency => _pool.maxConcurrency; + + @override + int get maxParallel => _pool.maxParallel; + + @override + int get maxSize => _pool.maxSize; + + @override + int get maxWorkers => _pool.maxWorkers; + + @override + int get maxWorkload => _pool.maxWorkload; + + @override + int get minWorkers => _pool.minWorkers; + + @override + int get pendingWorkload => _pool.pendingWorkload; + + @override + int get size => _pool.size; + + @override + Iterable get stats => _pool.stats; + + @override + bool get stopped => _pool.stopped; + + @override + int get totalErrors => _pool.totalErrors; + + @override + int get totalWorkload => _pool.totalWorkload; + + @override + int get workload => _pool.workload; + + @override + void cancel([Task? task, String? message]) => _pool.cancel(task, message); + + @override + FutureOr start() => _pool.start(); + + @override + int stop([bool Function(JsonDecodeServiceWorker worker)? predicate]) => + _pool.stop(predicate); + + @override + Object registerWorkerPoolListener( + void Function(JsonDecodeServiceWorker worker, bool removed) + listener) => + _pool.registerWorkerPoolListener(listener); + + @override + void unregisterWorkerPoolListener( + {void Function(JsonDecodeServiceWorker worker, bool removed)? + listener, + Object? token}) => + _pool.unregisterWorkerPoolListener(listener: listener, token: token); + + @override + Future execute(Future Function(JsonDecodeServiceWorker worker) task, + {PerfCounter? counter}) => + _pool.execute(task, counter: counter); + + @override + StreamTask scheduleStream( + Stream Function(JsonDecodeServiceWorker worker) task, + {PerfCounter? counter}) => + _pool.scheduleStream(task, counter: counter); + + @override + ValueTask scheduleTask( + Future Function(JsonDecodeServiceWorker worker) task, + {PerfCounter? counter}) => + _pool.scheduleTask(task, counter: counter); + + @override + Stream stream(Stream Function(JsonDecodeServiceWorker worker) task, + {PerfCounter? counter}) => + _pool.stream(task, counter: counter); + + @override + Object get _detachToken => _pool._detachToken; } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 963f23c9..5a1e090d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: analyzer: http: built_collection: - squadron: ^4.3.8 + squadron: ^5.0.0 dev_dependencies: build_runner: @@ -23,7 +23,7 @@ dev_dependencies: built_value_generator: dart_code_metrics: '>=4.8.1 <6.0.0' lints: ^2.0.0 - squadron_builder: ^2.0.0 + squadron_builder: ^2.1.2 dependency_overrides: chopper: From 05464527a2835fbc52d268ac8e8ff50a9e2b052e Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 27 May 2023 09:07:34 +0100 Subject: [PATCH 064/102] :arrow_up: update http constraint to ">=0.13.0 <2.0.0" (#431) --- chopper/pubspec.yaml | 2 +- chopper_built_value/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index f6cfea6a..6ef71aad 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: equatable: ^2.0.5 - http: ">=0.13.0 <1.0.0" + http: ">=0.13.0 <2.0.0" logging: ^1.0.0 meta: ^1.3.0 diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 1f536048..9d9a208e 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: built_value: ^8.0.0 built_collection: ^5.0.0 chopper: ^6.0.0 - http: ^0.13.0 + http: ">=0.13.0 <2.0.0" dev_dependencies: test: ^1.16.4 From ae8bff5ec2aa819f8b3ed86d8ffaa0df1195ae8c Mon Sep 17 00:00:00 2001 From: "Joseph, NamKung" Date: Sun, 28 May 2023 08:16:40 +0900 Subject: [PATCH 065/102] Add MultipartRequest log to CurlInterceptor (#435) --- chopper/lib/src/interceptor.dart | 10 ++++++++++ chopper/test/interceptors_test.dart | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 0e123237..b8595cea 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -151,6 +151,16 @@ class CurlInterceptor implements RequestInterceptor { curl += ' -d \'$body\''; } } + if (baseRequest is http.MultipartRequest) { + final fields = baseRequest.fields; + final files = baseRequest.files; + fields.forEach((k, v) { + curl += ' -f \'$k: $v\''; + }); + for (var file in files) { + curl += ' -f \'${file.field}: ${file.filename ?? ''}\''; + } + } curl += ' "$url"'; chopperLogger.info(curl); diff --git a/chopper/test/interceptors_test.dart b/chopper/test/interceptors_test.dart index 0584dcc5..3ee651d0 100644 --- a/chopper/test/interceptors_test.dart +++ b/chopper/test/interceptors_test.dart @@ -204,6 +204,35 @@ void main() { ), ); }); + + final fakeRequestMultipart = Request( + 'POST', + Uri.parse('/'), + Uri.parse('base'), + headers: {'foo': 'bar'}, + parts: [ + PartValue('p1', 123), + PartValueFile( + 'p2', + http.MultipartFile.fromBytes('file', [0], filename: 'filename'), + ), + ], + multipart: true, + ); + + test('Curl interceptors Multipart', () async { + final curl = CurlInterceptor(); + var log = ''; + chopperLogger.onRecord.listen((r) => log = r.message); + await curl.onRequest(fakeRequestMultipart); + + expect( + log, + equals( + "curl -v -X POST -H 'foo: bar' -f 'p1: 123' -f 'file: filename' \"base/\"", + ), + ); + }); }); } From d824ded15a9e5f83960bb57f438af53e2e9b5869 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 28 May 2023 08:55:54 +0100 Subject: [PATCH 066/102] :green_heart: add cleanup step to publish workflow (#434) --- .github/workflows/publish.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e29d5e21..664fde42 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -80,4 +80,9 @@ jobs: - 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 + run: echo "Skipping publish for ${{ matrix.package }} because the version is not greater than the one on pub.dev" + - name: Cleanup + id: cleanup + if: ${{ always() }} + run: | + rm -rf "$XDG_CONFIG_HOME/dart/pub-credentials.json" \ No newline at end of file From 829d5b0edd4986e3079fa81e67ebe9fbc836b6c2 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 28 May 2023 10:21:41 +0100 Subject: [PATCH 067/102] :twisted_rightwards_arrows: post release branch sync (#437) --- chopper/CHANGELOG.md | 57 +++++++++++++++++++------------- chopper/pubspec.yaml | 2 +- chopper_built_value/CHANGELOG.md | 4 +++ chopper_built_value/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 49 ++++++++++++++------------- example/pubspec.yaml | 2 +- 6 files changed, 67 insertions(+), 49 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index 8efd695f..e14d3af5 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,9 +1,17 @@ # Changelog +## 6.1.3 + +- Add follow redirects to toHttpRequest ([#430](https://github.com/lejard-h/chopper/pull/430)) +- Update http constraint to ">=0.13.0 <2.0.0" ([#431](https://github.com/lejard-h/chopper/pull/431)) +- Add MultipartRequest log to CurlInterceptor ([#435](https://github.com/lejard-h/chopper/pull/435)) + ## 6.1.2 + - Packages upgrade, constraints upgrade ## 6.1.1 + - EquatableMixin for Request, Response and PartValue ## 6.1.0 @@ -37,6 +45,7 @@ ## 4.0.1 - Fix for the null safety support + ## 4.0.0 - **Null safety support** @@ -73,15 +82,15 @@ **Breaking change** New way to handle errors - if (response.isSuccessful) { - final body = response.body; - } else { - final error = response.error; - } +if (response.isSuccessful) { +final body = response.body; +} else { +final error = response.error; +} + - Fix error handling by introducing `Response.error` getter - Remove `onError` since every response are available via `onResponse` stream - ## 2.5.0 - Unsuccessful response are not throw anymore, use `Response.isSuccessful` getter or `statusCode` instead @@ -90,8 +99,8 @@ New way to handle errors ## 2.4.2 - 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 + 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 - add `bool override` on `applyHeader(s)` functions, true by default @@ -107,8 +116,9 @@ New way to handle errors `Response.base` is now a `BaseRequest` instead of a `Request`, which means that you can't do base.body now. Please use Response.bodyBytes or Response.bodyString instead for non streaming case. - Now supports streams ! - - You can pass `Stream>` as a body to a request - - You can also use `Stream>` as the BodyType for the response, in this case the returned response will contain a stream in `body`. + - You can pass `Stream>` as a body to a request + - You can also use `Stream>` as the BodyType for the response, in this case the returned response will + contain a stream in `body`. - Support passing `MutlipartFile` (from packages:http) directly to `@FileField` annotation ## 2.3.2 @@ -138,12 +148,12 @@ New way to handle errors ## 2.2.0 - Fix converter issue on List - - ***Breaking Change*** - on `Converter.convertResponse(response)`, - it take a new generic type => `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 + thanks to @MichaelDark ## 2.1.0 @@ -159,30 +169,31 @@ thanks to @MichaelDark - Request is now containing baseUrl - Can call `Request.toHttpRequest()` direclty to get the `http.BaseRequest` will receive -- If a full url is specified in the `path` (ex: @Get(path: 'https://...')), it won't be concaten with the baseUrl of the ChopperClient and the ChopperAPI +- If a full url is specified in the `path` (ex: @Get(path: 'https://...')), it won't be concaten with the baseUrl of the + ChopperClient and the ChopperAPI - Add `CurlInterceptor` thanks @edwardaux - Add `HttpLoggingInterceptor` - Add `FactoryConverter` annotation `@FactoryConverter(request: convertRequest, response: convertResponse)` - ***BreakingChange*** - - Method.url renamed to path - - `Converter.encode` and `Converter.decode` removed, implement `Converter.convertResponse` and Converter.convertRequest` instead - - `ChopperClient.jsonApi` deprecated, use a `JsonConverter` instead - - `ChopperClient.formUrlEncodedApi`, use `FormUrlEncodedConverter` instead - - remove `JsonEncoded` annotation, use `FactoryConverter` instead + - Method.url renamed to path + - `Converter.encode` and `Converter.decode` removed, implement `Converter.convertResponse` and + Converter.convertRequest` instead + - `ChopperClient.jsonApi` deprecated, use a `JsonConverter` instead + - `ChopperClient.formUrlEncodedApi`, use `FormUrlEncodedConverter` instead + - remove `JsonEncoded` annotation, use `FactoryConverter` instead ## 1.1.0 - ***BreakingChange*** - Removed `name` parameter on `ChopperApi` - New way to instanciate a service + 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 - Multipart request diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 6ef71aad..322108e1 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: 6.1.2 +version: 6.1.3 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 c78c9e07..5fc1f1f7 100644 --- a/chopper_built_value/CHANGELOG.md +++ b/chopper_built_value/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.2 + +- Update http constraint to ">=0.13.0 <2.0.0" ([#431](https://github.com/lejard-h/chopper/pull/431)) + ## 1.2.1 - Packages upgrade, constraints upgrade diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 9d9a208e..1eed9725 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.2.1 +version: 1.2.2 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 4fa0cf97..2c58007d 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -36,13 +36,13 @@ ## 4.0.1 - Fix for the null safety support + ## 4.0.0 - **Null safety support** - Fix `@Header` annotation not generating null safe code - Respect `required` keyword in functions - ## 3.0.5 - Packages upgrade @@ -66,7 +66,8 @@ ## 3.0.0 -- Maintenance release to support last version of `chopper` package (3.0.0) that introduced a breaking change on error handling +- Maintenance release to support last version of `chopper` package (3.0.0) that introduced a breaking change on error + handling ## 2.5.0 @@ -76,8 +77,8 @@ ## 2.4.2 - 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 + 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 - add `bool override` on `applyHeader(s)` functions, true by default @@ -94,7 +95,7 @@ ## 2.3.4 - fix trailing slash when empty path +fix trailing slash when empty path ## 2.3.3 @@ -127,12 +128,12 @@ ## 2.2.0 - Fix converter issue on List - - ***Breaking Change*** - on `Converter.convertResponse(response)`, - it take a new generic type => `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 + thanks to @MichaelDark ## 2.1.0 @@ -142,29 +143,31 @@ thanks to @MichaelDark - Request is now containing baseUrl - Can call `Request.toHttpRequest()` direclty to get the `http.BaseRequest` will receive -- If a full url is specified in the `path` (ex: @Get(path: 'https://...')), it won't be concaten with the baseUrl of the ChopperClient and the ChopperAPI +- If a full url is specified in the `path` (ex: @Get(path: 'https://...')), it won't be concaten with the baseUrl of the + ChopperClient and the ChopperAPI - Add `CurlInterceptor` thanks @edwardaux - Add `HttpLoggingInterceptor` - Add `FactoryConverter` annotation `@FactoryConverter(request: convertRequest, response: convertResponse)` - ***BreakingChange*** - - Method.url renamed to path - - `Converter.encode` and `Converter.decode` removed, implement `Converter.convertResponse` and Converter.convertRequest` instead - - `ChopperClient.jsonApi` deprecated, use a `JsonConverter` instead - - `ChopperClient.formUrlEncodedApi`, use `FormUrlEncodedConverter` instead - - remove `JsonEncoded` annotation, use `FactoryConverter` instead + - Method.url renamed to path + - `Converter.encode` and `Converter.decode` removed, implement `Converter.convertResponse` and + Converter.convertRequest` instead + - `ChopperClient.jsonApi` deprecated, use a `JsonConverter` instead + - `ChopperClient.formUrlEncodedApi`, use `FormUrlEncodedConverter` instead + - remove `JsonEncoded` annotation, use `FactoryConverter` instead ## 1.1.0 - ***BreakingChange*** - Removed `name` parameter on `ChopperApi` - New way to instanciate a service - ```dart - @ChopperApi() - abstract class MyService extends ChopperService { - static MyService create([ChopperClient client]) => _$MyService(client); - } - ``` + Removed `name` parameter on `ChopperApi` + New way to instanciate a service + ```dart + @ChopperApi() + abstract class MyService extends ChopperService { + static MyService create([ChopperClient client]) => _$MyService(client); + } + ``` ## 1.0.1 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5a1e090d..d88b0adf 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.4 +version: 0.0.5 documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard From 455cd343923880817a46af2eba556dd4af4d7f8c Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 3 Jun 2023 10:07:39 +0100 Subject: [PATCH 068/102] :bug: fix Multipart for List and List (#439) --- chopper/lib/src/request.dart | 17 +- chopper/test/multipart_test.dart | 558 ++++++++++++++++--------- chopper/test/test_service.chopper.dart | 36 ++ chopper/test/test_service.dart | 9 + 4 files changed, 409 insertions(+), 211 deletions(-) diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index 7e0b21f2..736126ee 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -180,13 +180,18 @@ class Request extends http.BaseRequest with EquatableMixin { ); } 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)', + 'Type ${part.value.runtimeType} is not a supported type for PartFile. ' + 'Please use one of the following types:\n' + '- List\n' + '- String (path of your file)\n' + '- MultipartFile (from package:http)', ); } + } else if (part.value is Iterable) { + request.fields.addAll({ + for (int i = 0; i < part.value.length; i++) + '${part.name}[$i]': part.value.elementAt(i).toString(), + }); } else { request.fields[part.name] = part.value.toString(); } @@ -251,7 +256,7 @@ class PartValue with EquatableMixin { ]; } -/// Represents a file part in a multipart request. +/// Represents a file [PartValue] in a multipart request. @immutable class PartValueFile extends PartValue { const PartValueFile(super.name, super.value); diff --git a/chopper/test/multipart_test.dart b/chopper/test/multipart_test.dart index ebb8af90..8943a412 100644 --- a/chopper/test/multipart_test.dart +++ b/chopper/test/multipart_test.dart @@ -90,267 +90,415 @@ void main() { chopper.dispose(); }); - }); - test('file with MultipartFile', () async { - final httpClient = MockClient((http.Request req) async { - expect(req.headers['Content-Type'], contains('multipart/form-data;')); - expect( - req.body, - contains('content-type: application/octet-stream'), - ); + test('file with MultipartFile', () async { + final httpClient = MockClient((http.Request req) async { + expect(req.headers['Content-Type'], contains('multipart/form-data;')); + expect( + req.body, + contains('content-type: application/octet-stream'), + ); - expect( - req.body, - isNot(contains('content-disposition: form-data; name="id"')), - ); - expect( - 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])), + expect( + req.body, + isNot(contains('content-disposition: form-data; name="id"')), + ); + expect( + 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])), + ); + + return http.Response('ok', 200); + }); + + final chopper = ChopperClient(client: httpClient); + final service = HttpTestService.create(chopper); + + final file = http.MultipartFile.fromBytes( + 'file_field', + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: 'file_name', + contentType: MediaType.parse('application/octet-stream'), ); - return http.Response('ok', 200); + await service.postMultipartFile(file); + + chopper.dispose(); }); - final chopper = ChopperClient(client: httpClient); - final service = HttpTestService.create(chopper); + test('MultipartFile with other Part', () async { + final httpClient = MockClient((http.Request req) async { + expect(req.headers['Content-Type'], contains('multipart/form-data;')); - final file = http.MultipartFile.fromBytes( - 'file_field', - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - filename: 'file_name', - contentType: MediaType.parse('application/octet-stream'), - ); + expect( + req.body, + contains( + 'content-disposition: form-data; name="id"\r\n\r\n42\r\n', + ), + ); - await service.postMultipartFile(file); + expect( + req.body, + contains('content-type: application/octet-stream'), + ); + expect( + 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])), + ); - chopper.dispose(); - }); + return http.Response('ok', 200); + }); - test('MultipartFile with other Part', () async { - final httpClient = MockClient((http.Request req) async { - expect(req.headers['Content-Type'], contains('multipart/form-data;')); + final chopper = ChopperClient(client: httpClient); + final service = HttpTestService.create(chopper); - expect( - req.body, - contains( - 'content-disposition: form-data; name="id"\r\n\r\n42\r\n', - ), + final file = http.MultipartFile.fromBytes( + 'file_field', + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: 'file_name', + contentType: MediaType.parse('application/octet-stream'), ); - expect( - req.body, - contains('content-type: application/octet-stream'), - ); - expect( - 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])), - ); + await service.postMultipartFile(file, id: '42'); - return http.Response('ok', 200); + chopper.dispose(); }); - final chopper = ChopperClient(client: httpClient); - final service = HttpTestService.create(chopper); + test('support List', () async { + final httpClient = MockClient((http.Request req) async { + expect(req.headers['Content-Type'], contains('multipart/form-data;')); - final file = http.MultipartFile.fromBytes( - 'file_field', - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - filename: 'file_name', - contentType: MediaType.parse('application/octet-stream'), - ); + expect( + req.body, + contains( + 'content-type: application/octet-stream\r\n' + 'content-disposition: form-data; name="file_1"; filename="file_name_1"\r\n' + '\r\n' + 'Hello', + ), + ); - await service.postMultipartFile(file, id: '42'); + expect( + req.body, + contains( + 'content-type: application/octet-stream\r\n' + 'content-disposition: form-data; name="file_2"; filename="file_name_2"\r\n' + '\r\n' + 'World', + ), + ); - chopper.dispose(); - }); + return http.Response('ok', 200); + }); - test('support List', () async { - final httpClient = MockClient((http.Request req) async { - expect(req.headers['Content-Type'], contains('multipart/form-data;')); + final chopper = ChopperClient(client: httpClient); + final service = HttpTestService.create(chopper); - expect( - req.body, - contains( - 'content-type: application/octet-stream\r\n' - 'content-disposition: form-data; name="file_1"; filename="file_name_1"\r\n' - '\r\n' - 'Hello', - ), + final file1 = http.MultipartFile.fromBytes( + 'file_1', + 'Hello'.codeUnits, + filename: 'file_name_1', + contentType: MediaType.parse('application/octet-stream'), ); - expect( - req.body, - contains( - 'content-type: application/octet-stream\r\n' - 'content-disposition: form-data; name="file_2"; filename="file_name_2"\r\n' - '\r\n' - 'World', - ), + final file2 = http.MultipartFile.fromBytes( + 'file_2', + 'World'.codeUnits, + filename: 'file_name_2', + contentType: MediaType.parse('application/octet-stream'), ); - return http.Response('ok', 200); + await service.postListFiles([file1, file2]); + + chopper.dispose(); }); - final chopper = ChopperClient(client: httpClient); - final service = HttpTestService.create(chopper); + test('PartValue', () async { + final req = await Request( + HttpMethod.Post, + Uri.parse('https://foo/'), + Uri.parse(''), + parts: [ + PartValue('foo', 'bar'), + PartValue('int', 42), + ], + ).toMultipartRequest(); - final file1 = http.MultipartFile.fromBytes( - 'file_1', - 'Hello'.codeUnits, - filename: 'file_name_1', - contentType: MediaType.parse('application/octet-stream'), - ); + expect(req.fields['foo'], equals('bar')); + expect(req.fields['int'], equals('42')); + }); + + test( + 'PartFile', + () async { + final req = await Request( + HttpMethod.Post, + Uri.parse('https://foo/'), + Uri.parse(''), + parts: [ + PartValueFile('foo', 'test/multipart_test.dart'), + PartValueFile>('int', [1, 2]), + ], + ).toMultipartRequest(); - final file2 = http.MultipartFile.fromBytes( - 'file_2', - 'World'.codeUnits, - filename: 'file_name_2', - contentType: MediaType.parse('application/octet-stream'), + 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', ); - await service.postListFiles([file1, file2]); + test('PartValue.replace', () { + dynamic part = PartValue('foo', 'bar'); - chopper.dispose(); - }); + expect(part.name, equals('foo')); + expect(part.value, equals('bar')); - test('PartValue', () async { - final req = await Request( - HttpMethod.Post, - Uri.parse('https://foo/'), - Uri.parse(''), - parts: [ - PartValue('foo', 'bar'), - PartValue('int', 42), - ], - ).toMultipartRequest(); - - expect(req.fields['foo'], equals('bar')); - expect(req.fields['int'], equals('42')); - }); + part = part.copyWith(value: 42); + + expect(part.name, equals('foo')); + expect(part.value, equals(42)); - test( - 'PartFile', - () async { + part = part.copyWith(name: 'int'); + + expect(part.name, equals('int')); + expect(part.value, equals(42)); + }); + + test('Multipart request non nullable', () async { final req = await Request( HttpMethod.Post, Uri.parse('https://foo/'), Uri.parse(''), parts: [ - PartValueFile('foo', 'test/multipart_test.dart'), - PartValueFile>('int', [1, 2]), + PartValue('int', 42), + PartValueFile>('list int', [1, 2]), + PartValue('null value', null), + PartValueFile('null file', null), ], ).toMultipartRequest(); + expect(req.fields.length, equals(1)); + expect(req.fields['int'], equals('42')); + expect(req.files.length, equals(1)); + final bytes = await req.files.first.finalize().first; + expect(bytes, equals([1, 2])); + }); + + test('PartValue with MultipartFile directly', () async { + final req = await Request( + HttpMethod.Post, + Uri.parse('https://foo/'), + Uri.parse(''), + parts: [ + PartValue( + '', + http.MultipartFile.fromBytes( + 'first', + [1, 2], + filename: 'list int 1', + ), + ), + PartValueFile( + '', + http.MultipartFile.fromBytes( + 'second', + [2, 1], + filename: 'list int 2', + ), + ), + ], + ).toMultipartRequest(); + + final first = req.files[0]; + final second = req.files[1]; + + expect(first.filename, equals('list int 1')); + expect(second.filename, equals('list int 2')); + + var bytes = await first.finalize().first; + expect(bytes, equals([1, 2])); + + bytes = await second.finalize().first; + expect(bytes, equals([2, 1])); + }); + + test('Throw exception', () async { expect( - req.files.firstWhere((f) => f.field == 'foo').filename, - equals('multipart_test.dart'), + () async => await Request( + HttpMethod.Post, + Uri.parse('https://foo/'), + Uri.parse(''), + parts: [ + PartValueFile('', 123), + ], + ).toMultipartRequest(), + throwsA(isA()), ); - 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'); + test('Multipart request List', () async { + const List ints = [1, 2, 3]; + const List doubles = [1.23, -1.23, 0.0, 0.12324, 3 / 4]; + const List nums = [ + 1.23443534678, + 0.00000000001, + -34251, + 0.0, + 3 / 4, + ]; + const List strings = [ + 'lorem', + 'ipsum', + 'dolor', + '''r237tw78re ei[04o2 ]de[qwlr;,mgrrt9ie0owp[ld;s,a.vfe[plre'q/sd;poeßšđčćž''', + ]; - expect(part.name, equals('foo')); - expect(part.value, equals('bar')); + final req = await Request( + HttpMethod.Post, + Uri.parse('https://foo/'), + Uri.parse(''), + parts: [ + PartValue>('ints', ints), + PartValue>('doubles', doubles), + PartValue>('nums', nums), + PartValue>('strings', strings), + ], + ).toMultipartRequest(); - part = part.copyWith(value: 42); + expect( + req.fields.length, + equals(ints.length + doubles.length + nums.length + strings.length), + ); - expect(part.name, equals('foo')); - expect(part.value, equals(42)); + for (var i = 0; i < ints.length; i++) { + expect(req.fields['ints[$i]'], equals(ints[i].toString())); + } - part = part.copyWith(name: 'int'); + for (var i = 0; i < doubles.length; i++) { + expect(req.fields['doubles[$i]'], equals(doubles[i].toString())); + } - expect(part.name, equals('int')); - expect(part.value, equals(42)); - }); + for (var i = 0; i < nums.length; i++) { + expect(req.fields['nums[$i]'], equals(nums[i].toString())); + } - test('Multipart request non nullable', () async { - final req = await Request( - HttpMethod.Post, - Uri.parse('https://foo/'), - Uri.parse(''), - parts: [ - PartValue('int', 42), - PartValueFile>('list int', [1, 2]), - PartValue('null value', null), - PartValueFile('null file', null), - ], - ).toMultipartRequest(); - - expect(req.fields.length, equals(1)); - expect(req.fields['int'], equals('42')); - expect(req.files.length, equals(1)); - final bytes = await req.files.first.finalize().first; - expect(bytes, equals([1, 2])); - }); + for (var i = 0; i < strings.length; i++) { + expect(req.fields['strings[$i]'], equals(strings[i])); + } + }); - test('PartValue with MultipartFile directly', () async { - final req = await Request( - HttpMethod.Post, - Uri.parse('https://foo/'), - Uri.parse(''), - parts: [ - PartValue( - '', - http.MultipartFile.fromBytes( - 'first', - [1, 2], - filename: 'list int 1', - ), - ), - PartValueFile( - '', - http.MultipartFile.fromBytes( - 'second', - [2, 1], - filename: 'list int 2', - ), - ), - ], - ).toMultipartRequest(); + test('Multipart lists', () async { + const List ints = [1, 2, 3]; + const List doubles = [1.23, -1.23, 0.0, 0.12324, 3 / 4]; + const List nums = [ + 1.23443534678, + 0.00000000001, + -34251, + 0.0, + 3 / 4, + ]; + const String utf8String = + '''r237tw78re ei[04o2 ]de[qwlr;,mgrrt9ie0owp[ld;s,a.vfe[plre'q/sd;poeßšđčćž'''; + const List strings = [ + 'lorem', + 'ipsum', + 'dolor', + utf8String, + ]; - final first = req.files[0]; - final second = req.files[1]; + final httpClient = MockClient((http.Request req) async { + expect(req.headers['Content-Type'], contains('multipart/form-data;')); - expect(first.filename, equals('list int 1')); - expect(second.filename, equals('list int 2')); + for (var i = 0; i < ints.length; i++) { + expect( + req.body, + contains( + 'content-disposition: form-data; name="ints[$i]"\r\n' + '\r\n' + '${ints[i]}\r\n', + ), + ); + } + + for (var i = 0; i < doubles.length; i++) { + expect( + req.body, + contains( + 'content-disposition: form-data; name="doubles[$i]"\r\n' + '\r\n' + '${doubles[i]}\r\n', + ), + ); + } + + for (var i = 0; i < nums.length; i++) { + expect( + req.body, + contains( + 'content-disposition: form-data; name="nums[$i]"\r\n' + '\r\n' + '${nums[i]}\r\n', + ), + ); + } + + for (var i = 0; i < strings.length; i++) { + if (strings[i] == utf8String) { + expect( + req.body, + contains( + 'content-disposition: form-data; name="strings[$i]"\r\n' + 'content-type: text/plain; charset=utf-8\r\n' + 'content-transfer-encoding: binary\r\n' + '\r\n' + '${strings[i]}\r\n', + ), + ); + } else { + expect( + req.body, + contains( + 'content-disposition: form-data; name="strings[$i]"\r\n' + '\r\n' + '${strings[i]}\r\n', + ), + ); + } + } - var bytes = await first.finalize().first; - expect(bytes, equals([1, 2])); + return http.Response('ok', 200); + }); - bytes = await second.finalize().first; - expect(bytes, equals([2, 1])); - }); + final chopper = ChopperClient(client: httpClient); + final service = HttpTestService.create(chopper); - test('Throw exception', () async { - expect( - () async => await Request( - HttpMethod.Post, - Uri.parse('https://foo/'), - Uri.parse(''), - parts: [ - PartValueFile('', 123), - ], - ).toMultipartRequest(), - throwsA(isA()), - ); + await service.postMultipartList( + ints: ints, + doubles: doubles, + nums: nums, + strings: strings, + ); + + chopper.dispose(); + }); }); } diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index f93398a5..45e1d737 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -475,6 +475,42 @@ class _$HttpTestService extends HttpTestService { return client.send($request); } + @override + Future> postMultipartList({ + required List ints, + required List doubles, + required List nums, + required List strings, + }) { + final Uri $url = Uri.parse('/test/multipart_list'); + final List $parts = [ + PartValue>( + 'ints', + ints, + ), + PartValue>( + 'doubles', + doubles, + ), + PartValue>( + 'nums', + nums, + ), + PartValue>( + 'strings', + strings, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + @override Future fullUrl() { final Uri $url = Uri.parse('https://test.com'); diff --git a/chopper/test/test_service.dart b/chopper/test/test_service.dart index 6257f43a..c0baf1a4 100644 --- a/chopper/test/test_service.dart +++ b/chopper/test/test_service.dart @@ -136,6 +136,15 @@ abstract class HttpTestService extends ChopperService { @multipart Future postListFiles(@PartFile() List files); + @Post(path: 'multipart_list') + @multipart + Future postMultipartList({ + @Part('ints') required List ints, + @Part('doubles') required List doubles, + @Part('nums') required List nums, + @Part('strings') required List strings, + }); + @Get(path: 'https://test.com') Future fullUrl(); From 1f8c59630c5342310b138e4585cc38fad97f22a5 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 4 Jun 2023 09:04:54 +0100 Subject: [PATCH 069/102] :twisted_rightwards_arrows: post release branch sync (#441) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index e14d3af5..d8a56152 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 6.1.4 + +- Fix Multipart for List and List ([#439](https://github.com/lejard-h/chopper/pull/439)) + ## 6.1.3 - Add follow redirects to toHttpRequest ([#430](https://github.com/lejard-h/chopper/pull/430)) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 322108e1..4fd47d0a 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: 6.1.3 +version: 6.1.4 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 917a463541078df5eb25622cc1daaeb70e959542 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 19 Jun 2023 16:40:34 +0100 Subject: [PATCH 070/102] :sparkles: Generating files in different directories (#444) --- chopper_generator/lib/chopper_generator.dart | 2 +- chopper_generator/lib/src/generator.dart | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/chopper_generator/lib/chopper_generator.dart b/chopper_generator/lib/chopper_generator.dart index 74355389..84099889 100644 --- a/chopper_generator/lib/chopper_generator.dart +++ b/chopper_generator/lib/chopper_generator.dart @@ -5,4 +5,4 @@ import 'package:build/build.dart'; import 'src/generator.dart'; Builder chopperGeneratorFactory(BuilderOptions options) => - chopperGeneratorFactoryBuilder(header: options.config['header']); + chopperGeneratorFactoryBuilder(options); diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 72635a75..cb3b4f2f 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -634,10 +634,19 @@ class ChopperGenerator extends GeneratorForAnnotation { } } -Builder chopperGeneratorFactoryBuilder({String? header}) => PartBuilder( +Builder chopperGeneratorFactoryBuilder(BuilderOptions options) => PartBuilder( [ChopperGenerator()], '.chopper.dart', - header: header, + header: options.config['header'], + formatOutput: + PartBuilder([ChopperGenerator()], '.chopper.dart').formatOutput, + options: !options.config.containsKey('build_extensions') + ? options.overrideWith( + BuilderOptions({ + 'build_extensions': {'.dart': '.chopper.dart'}, + }), + ) + : options, ); bool getMethodOptionalBody(ConstantReader method) => From ac907839cb79a187046bffc56fcf3fd834290534 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 19 Jun 2023 17:15:01 +0100 Subject: [PATCH 071/102] :twisted_rightwards_arrows: post release branch sync (#447) --- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 2c58007d..881ab59e 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 6.0.2 + +- Add support for generating files in different directories ([#444](https://github.com/lejard-h/chopper/pull/444)) + ## 6.0.1 - Packages upgrade, constraints upgrade diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 716f5332..ee10e5de 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: 6.0.1 +version: 6.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 97959f0773d40e0e3ce21fd87a6e025e283012ad Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Tue, 4 Jul 2023 22:01:47 +0100 Subject: [PATCH 072/102] :recycle: refactor chopper_generator (#448) --- .github/workflows/dart.yml | 21 +- chopper/pubspec.yaml | 1 + chopper/test/ensure_build_test.dart | 16 + chopper_generator/Makefile | 4 + chopper_generator/README.md | 9 + chopper_generator/lib/chopper_generator.dart | 7 +- .../lib/src/builder_factory.dart | 23 + chopper_generator/lib/src/extensions.dart | 6 + chopper_generator/lib/src/generator.dart | 380 +++++------ chopper_generator/lib/src/utils.dart | 61 ++ chopper_generator/lib/src/vars.dart | 17 + chopper_generator/mono_pkg.yaml | 2 + chopper_generator/pubspec.yaml | 5 +- chopper_generator/test/ensure_build_test.dart | 16 + .../test/test_service.chopper.dart | 639 ++++++++++++++++++ chopper_generator/test/test_service.dart | 218 ++++++ 16 files changed, 1191 insertions(+), 234 deletions(-) create mode 100644 chopper/test/ensure_build_test.dart create mode 100644 chopper_generator/lib/src/builder_factory.dart create mode 100644 chopper_generator/lib/src/extensions.dart create mode 100644 chopper_generator/lib/src/utils.dart create mode 100644 chopper_generator/lib/src/vars.dart create mode 100644 chopper_generator/test/ensure_build_test.dart create mode 100644 chopper_generator/test/test_service.chopper.dart create mode 100644 chopper_generator/test/test_service.dart diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 444801b6..ee10d7f9 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -127,16 +127,16 @@ jobs: - job_001 - job_002 job_004: - name: "unit_test; PKGS: chopper, chopper_built_value; `dart pub global run coverage:test_with_coverage`" + name: "unit_test; PKGS: chopper, chopper_built_value, chopper_generator; `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_with_coverage" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value-chopper_generator;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;packages:chopper-chopper_built_value-chopper_generator os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest @@ -179,6 +179,21 @@ jobs: files: chopper_built_value/coverage/lcov.info fail_ci_if_error: true name: coverage_01 + - 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 + - name: "chopper_generator; dart pub global run coverage:test_with_coverage" + run: "dart pub global run coverage:test_with_coverage" + if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" + working-directory: chopper_generator + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@main + with: + files: chopper_generator/coverage/lcov.info + fail_ci_if_error: true + name: coverage_02 needs: - job_001 - job_002 diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 4fd47d0a..2af5cfbf 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 + build_verify: ^3.1.0 collection: ^1.16.0 coverage: ^1.0.2 dart_code_metrics: '>=4.8.1 <6.0.0' diff --git a/chopper/test/ensure_build_test.dart b/chopper/test/ensure_build_test.dart new file mode 100644 index 00000000..73f46337 --- /dev/null +++ b/chopper/test/ensure_build_test.dart @@ -0,0 +1,16 @@ +@TestOn('vm') +@Timeout(Duration(seconds: 120)) +import 'package:build_verify/build_verify.dart'; +import 'package:test/test.dart'; + +void main() { + test( + 'ensure_build', + () => expectBuildClean( + packageRelativeDirectory: 'chopper', + gitDiffPathArguments: [ + 'test/test_service.chopper.dart', + ], + ), + ); +} diff --git a/chopper_generator/Makefile b/chopper_generator/Makefile index dee4bcf7..444451d2 100644 --- a/chopper_generator/Makefile +++ b/chopper_generator/Makefile @@ -41,6 +41,10 @@ install: @# Help: Install all the project's packages dart pub get +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/README.md b/chopper_generator/README.md index 63f60659..81a0f90f 100644 --- a/chopper_generator/README.md +++ b/chopper_generator/README.md @@ -1 +1,10 @@ +# chopper_generator + +[![pub package](https://img.shields.io/pub/v/chopper_generator.svg)](https://pub.dartlang.org/packages/chopper_generator) + This package provides the code generator for the [Chopper](https://github.com/lejard-h/chopper) package. + +## Usage + +For examples please refer to the main [Chopper](https://github.com/lejard-h/chopper) package and/or read the +[documentation](https://hadrien-lejard.gitbook.io/chopper). \ No newline at end of file diff --git a/chopper_generator/lib/chopper_generator.dart b/chopper_generator/lib/chopper_generator.dart index 84099889..df3d5954 100644 --- a/chopper_generator/lib/chopper_generator.dart +++ b/chopper_generator/lib/chopper_generator.dart @@ -1,8 +1,3 @@ library chopper_generator.dart; -import 'package:build/build.dart'; - -import 'src/generator.dart'; - -Builder chopperGeneratorFactory(BuilderOptions options) => - chopperGeneratorFactoryBuilder(options); +export 'src/builder_factory.dart'; diff --git a/chopper_generator/lib/src/builder_factory.dart b/chopper_generator/lib/src/builder_factory.dart new file mode 100644 index 00000000..f5ff89d1 --- /dev/null +++ b/chopper_generator/lib/src/builder_factory.dart @@ -0,0 +1,23 @@ +import 'package:build/build.dart'; +import 'package:chopper/chopper.dart' show ChopperApi; +import 'package:source_gen/source_gen.dart'; + +import 'generator.dart'; + +/// Creates a [PartBuilder] used to generate code for [ChopperApi] annotated +/// classes. The [options] are provided by Dart's build system and read from the +/// `build.yaml` file. +Builder chopperGeneratorFactory(BuilderOptions options) => PartBuilder( + [const ChopperGenerator()], + '.chopper.dart', + header: options.config['header'], + formatOutput: + PartBuilder([const ChopperGenerator()], '.chopper.dart').formatOutput, + options: !options.config.containsKey('build_extensions') + ? options.overrideWith( + BuilderOptions({ + 'build_extensions': {'.dart': '.chopper.dart'}, + }), + ) + : options, + ); diff --git a/chopper_generator/lib/src/extensions.dart b/chopper_generator/lib/src/extensions.dart new file mode 100644 index 00000000..8361baf6 --- /dev/null +++ b/chopper_generator/lib/src/extensions.dart @@ -0,0 +1,6 @@ +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; + +extension DartTypeExtension on DartType { + bool get isNullable => nullabilitySuffix != NullabilitySuffix.none; +} diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index cb3b4f2f..39353006 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -1,30 +1,25 @@ -///@nodoc -import 'dart:async'; +import 'dart:async' show FutureOr; 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:chopper/chopper.dart' as chopper; +import 'package:chopper_generator/src/extensions.dart'; +import 'package:chopper_generator/src/utils.dart'; +import 'package:chopper_generator/src/vars.dart'; 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 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'; +/// Code generator for [chopper.ChopperApi] annotated classes. +class ChopperGenerator extends GeneratorForAnnotation { + const ChopperGenerator(); -final Logger _logger = Logger('Chopper Generator'); + static final Logger _logger = Logger('Chopper Generator'); -class ChopperGenerator extends GeneratorForAnnotation { @override FutureOr generateForAnnotatedElement( Element element, @@ -32,20 +27,20 @@ class ChopperGenerator extends GeneratorForAnnotation { BuildStep buildStep, ) { if (element is! ClassElement) { - final String friendlyName = element.displayName; throw InvalidGenerationSourceError( - 'Generator cannot target `$friendlyName`.', - todo: 'Remove the [ChopperApi] annotation from `$friendlyName`.', + 'Generator cannot target `${element.displayName}`.', + todo: + 'Remove the [ChopperApi] annotation from `${element.displayName}`.', ); } return _buildChopperApiImplementationClass(annotation, element); } - bool _extendsChopperService(InterfaceType type) => + static bool _extendsChopperService(InterfaceType type) => _typeChecker(chopper.ChopperService).isExactlyType(type); - Field _buildDefinitionTypeMethod(String superType) => Field( + static Field _buildDefinitionTypeMethod(String superType) => Field( (method) => method ..annotations.add(refer('override')) ..name = 'definitionType' @@ -53,7 +48,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ..assignment = Code(superType), ); - String _buildChopperApiImplementationClass( + static String _buildChopperApiImplementationClass( ConstantReader annotation, ClassElement element, ) { @@ -67,7 +62,8 @@ class ChopperGenerator extends GeneratorForAnnotation { final String friendlyName = element.name; final String name = '_\$$friendlyName'; - final String baseUrl = annotation.peek(_baseUrlVar)?.stringValue ?? ''; + final String baseUrl = + annotation.peek(Vars.baseUrl.toString())?.stringValue ?? ''; final Class classBuilder = Class((builder) { builder @@ -78,29 +74,33 @@ class ChopperGenerator extends GeneratorForAnnotation { ..methods.addAll(_parseMethods(element, baseUrl)); }); - final String ignore = - '// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps'; + const String ignore = '// ignore_for_file: ' + 'always_put_control_body_on_new_line, ' + 'always_specify_types, ' + 'prefer_const_declarations, ' + 'unnecessary_brace_in_string_interps'; final DartEmitter emitter = DartEmitter(); return DartFormatter().format('$ignore\n${classBuilder.accept(emitter)}'); } - Constructor _generateConstructor() => Constructor( + static Constructor _generateConstructor() => Constructor( (ConstructorBuilder constructorBuilder) { constructorBuilder.optionalParameters.add( Parameter((paramBuilder) { - paramBuilder.name = _clientVar; + paramBuilder.name = Vars.client.toString(); paramBuilder.type = refer('${chopper.ChopperClient}?'); }), ); constructorBuilder.body = Code( - 'if ($_clientVar == null) return;\nthis.$_clientVar = $_clientVar;', + 'if (${Vars.client} == null) return;\n' + 'this.${Vars.client} = ${Vars.client};', ); }, ); - Iterable _parseMethods(ClassElement element, String baseUrl) => + static Iterable _parseMethods(ClassElement element, String baseUrl) => element.methods .where( (MethodElement method) => @@ -110,7 +110,7 @@ class ChopperGenerator extends GeneratorForAnnotation { ) .map((MethodElement m) => _generateMethod(m, baseUrl)); - Method _generateMethod(MethodElement m, String baseUrl) { + static Method _generateMethod(MethodElement m, String baseUrl) { final ConstantReader? method = _getMethodAnnotation(m); final bool multipart = _hasAnnotation(m, chopper.Multipart); final ConstantReader? factoryConverter = _getFactoryConverterAnnotation(m); @@ -145,12 +145,12 @@ class ChopperGenerator extends GeneratorForAnnotation { b.annotations.add(refer('override')); b.name = m.displayName; - // We don't support returning null Type + /// We don't support returning null Type b.returns = Reference( m.returnType.getDisplayString(withNullability: false), ); - // And null Typed parameters + /// And null Typed parameters b.types.addAll( m.typeParameters.map( (t) => Reference(t.getDisplayString(withNullability: false)), @@ -160,32 +160,35 @@ class ChopperGenerator extends GeneratorForAnnotation { b.requiredParameters.addAll( m.parameters .where((p) => p.isRequiredPositional) - .map(buildRequiredPositionalParam), + .map(Utils.buildRequiredPositionalParam), ); b.optionalParameters.addAll( m.parameters .where((p) => p.isOptionalPositional) - .map(buildOptionalPositionalParam), + .map(Utils.buildOptionalPositionalParam), ); b.optionalParameters.addAll( - m.parameters.where((p) => p.isNamed).map(buildNamedParam), + m.parameters.where((p) => p.isNamed).map(Utils.buildNamedParam), ); final List blocks = [ - declareFinal(_urlVar, type: refer('Uri')).assign(url).statement, + declareFinal(Vars.url.toString(), type: refer('Uri')) + .assign(url) + .statement, ]; if (queries.isNotEmpty) { blocks.add( - declareFinal(_parametersVar, type: refer('Map')) - .assign(_generateMap(queries)) - .statement, + declareFinal( + Vars.parameters.toString(), + type: refer('Map'), + ).assign(_generateMap(queries)).statement, ); } - // Build an iterable of all the parameters that are nullable + /// Build an iterable of all the parameters that are nullable final Iterable optionalNullableParameters = [ ...m.parameters.where((p) => p.isOptionalPositional), ...m.parameters.where((p) => p.isNamed), @@ -194,9 +197,9 @@ class ChopperGenerator extends GeneratorForAnnotation { final bool hasQueryMap = queryMap.isNotEmpty; if (hasQueryMap) { if (queries.isNotEmpty) { - blocks.add(refer('$_parametersVar.addAll').call( + blocks.add(refer('${Vars.parameters}.addAll').call( [ - // Check if the parameter is nullable + /// Check if the parameter is nullable optionalNullableParameters.contains(queryMap.keys.first) ? refer(queryMap.keys.first).ifNullThen(refer('const {}')) : refer(queryMap.keys.first), @@ -204,9 +207,12 @@ class ChopperGenerator extends GeneratorForAnnotation { ).statement); } else { blocks.add( - declareFinal(_parametersVar, type: refer('Map')) + declareFinal( + Vars.parameters.toString(), + type: refer('Map'), + ) .assign( - // Check if the parameter is nullable + /// Check if the parameter is nullable optionalNullableParameters.contains(queryMap.keys.first) ? refer(queryMap.keys.first).ifNullThen(refer('const {}')) : refer(queryMap.keys.first), @@ -222,18 +228,22 @@ class ChopperGenerator extends GeneratorForAnnotation { blocks.add(headers); } - final bool methodOptionalBody = getMethodOptionalBody(method); - final String methodName = getMethodName(method); - final String methodUrl = getMethodPath(method); + final bool methodOptionalBody = Utils.getMethodOptionalBody(method); + final String methodName = Utils.getMethodName(method); + final String methodUrl = Utils.getMethodPath(method); bool hasBody = body.isNotEmpty || fields.isNotEmpty; if (hasBody) { if (body.isNotEmpty) { blocks.add( - declareFinal(_bodyVar).assign(refer(body.keys.first)).statement, + declareFinal(Vars.body.toString()) + .assign(refer(body.keys.first)) + .statement, ); } else { blocks.add( - declareFinal(_bodyVar).assign(_generateMap(fields)).statement, + declareFinal(Vars.body.toString()) + .assign(_generateMap(fields)) + .statement, ); } } @@ -241,12 +251,14 @@ class ChopperGenerator extends GeneratorForAnnotation { final bool hasFieldMap = fieldMap.isNotEmpty; if (hasFieldMap) { if (hasBody) { - blocks.add(refer('$_bodyVar.addAll').call( + blocks.add(refer('${Vars.body}.addAll').call( [refer(fieldMap.keys.first)], ).statement); } else { blocks.add( - declareFinal(_bodyVar).assign(refer(fieldMap.keys.first)).statement, + declareFinal(Vars.body.toString()) + .assign(refer(fieldMap.keys.first)) + .statement, ); } } @@ -256,7 +268,7 @@ class ChopperGenerator extends GeneratorForAnnotation { bool hasParts = multipart && (parts.isNotEmpty || fileFields.isNotEmpty); if (hasParts) { blocks.add( - declareFinal(_partsVar, type: refer('List')) + declareFinal(Vars.parts.toString(), type: refer('List')) .assign(_generateList(parts, fileFields)) .statement, ); @@ -266,13 +278,13 @@ class ChopperGenerator extends GeneratorForAnnotation { if (hasPartMap) { if (hasParts) { blocks.add( - refer('$_partsVar.addAll').call( + refer('${Vars.parts}.addAll').call( [refer(partMap.keys.first)], ).statement, ); } else { blocks.add( - declareFinal(_partsVar, type: refer('List')) + declareFinal(Vars.parts.toString(), type: refer('List')) .assign(refer(partMap.keys.first)) .statement, ); @@ -283,13 +295,13 @@ class ChopperGenerator extends GeneratorForAnnotation { if (hasFileFilesMap) { if (hasParts || hasPartMap) { blocks.add( - refer('$_partsVar.addAll').call( + refer('${Vars.parts}.addAll').call( [refer(fileFieldMap.keys.first)], ).statement, ); } else { blocks.add( - declareFinal(_partsVar, type: refer('List')) + declareFinal(Vars.parts.toString(), type: refer('List')) .assign(refer(fileFieldMap.keys.first)) .statement, ); @@ -309,12 +321,12 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } - final bool useBrackets = getUseBrackets(method); + final bool useBrackets = Utils.getUseBrackets(method); - final bool includeNullQueryVars = getIncludeNullQueryVars(method); + final bool includeNullQueryVars = Utils.getIncludeNullQueryVars(method); blocks.add( - declareFinal(_requestVar, type: refer('Request')) + declareFinal(Vars.request.toString(), type: refer('Request')) .assign( _generateRequest( method, @@ -355,8 +367,8 @@ class ChopperGenerator extends GeneratorForAnnotation { ); } - blocks.add(refer('$_clientVar.send') - .call([refer(_requestVar)], namedArguments, typeArguments) + blocks.add(refer('${Vars.client}.send') + .call([refer(Vars.request.toString())], namedArguments, typeArguments) .returned .statement); @@ -366,19 +378,22 @@ 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) => + static String _factoryForFunction(FunctionTypedElement function) => // ignore: deprecated_member_use function.enclosingElement3 is ClassElement // ignore: deprecated_member_use ? '${function.enclosingElement3!.name}.${function.name}' : function.name!; - Map _getAnnotation(MethodElement method, Type type) { + static Map _getAnnotation( + MethodElement method, + Type type, + ) { DartObject? annotation; String name = ''; for (final ParameterElement p in method.parameters) { - DartObject? a = _typeChecker(type).firstAnnotationOf(p); + final DartObject? a = _typeChecker(type).firstAnnotationOf(p); if (annotation != null && a != null) { throw Exception( 'Too many $type annotation for \'${method.displayName}\'', @@ -392,25 +407,20 @@ class ChopperGenerator extends GeneratorForAnnotation { return annotation == null ? {} : {name: ConstantReader(annotation)}; } - Map _getAnnotations( + static Map _getAnnotations( 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; - } + ) => + { + for (final ParameterElement p in m.parameters) + if (_typeChecker(type).hasAnnotationOf(p)) + p: ConstantReader(_typeChecker(type).firstAnnotationOf(p)), + }; - TypeChecker _typeChecker(Type type) => TypeChecker.fromRuntime(type); + static TypeChecker _typeChecker(Type type) => TypeChecker.fromRuntime(type); - ConstantReader? _getMethodAnnotation(MethodElement method) { - for (final type in _methodsAnnotations) { + static ConstantReader? _getMethodAnnotation(MethodElement method) { + for (final Type type in _methodsAnnotations) { final DartObject? annotation = _typeChecker(type) .firstAnnotationOf(method, throwOnUnresolved: false); if (annotation != null) { @@ -421,18 +431,18 @@ class ChopperGenerator extends GeneratorForAnnotation { return null; } - ConstantReader? _getFactoryConverterAnnotation(MethodElement method) { + static ConstantReader? _getFactoryConverterAnnotation(MethodElement method) { final DartObject? annotation = _typeChecker(chopper.FactoryConverter) .firstAnnotationOf(method, throwOnUnresolved: false); return annotation != null ? ConstantReader(annotation) : null; } - bool _hasAnnotation(MethodElement method, Type type) => + static bool _hasAnnotation(MethodElement method, Type type) => _typeChecker(type).firstAnnotationOf(method, throwOnUnresolved: false) != null; - final List _methodsAnnotations = const [ + static const List _methodsAnnotations = [ chopper.Get, chopper.Post, chopper.Delete, @@ -443,14 +453,15 @@ class ChopperGenerator extends GeneratorForAnnotation { chopper.Options, ]; - DartType? _genericOf(DartType? type) => + static DartType? _genericOf(DartType? type) => type is InterfaceType && type.typeArguments.isNotEmpty ? type.typeArguments.first : null; - DartType? _getResponseType(DartType type) => _genericOf(_genericOf(type)); + static DartType? _getResponseType(DartType type) => + _genericOf(_genericOf(type)); - DartType? _getResponseInnerType(DartType type) { + static DartType? _getResponseInnerType(DartType type) { final DartType? generic = _genericOf(type); if (generic == null || @@ -466,41 +477,41 @@ class ChopperGenerator extends GeneratorForAnnotation { return _getResponseInnerType(generic); } - Expression _generateUrl( + static Expression _generateUrl( ConstantReader method, Map paths, String baseUrl, ) { - String path = getMethodPath(method); + String path = Utils.getMethodPath(method); paths.forEach((p, ConstantReader r) { final String name = r.peek('name')?.stringValue ?? p.displayName; path = path.replaceFirst('{$name}', '\${${p.displayName}}'); }); 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 + /// if the request's url is already a fully qualified URL, we can use + /// as-is and ignore the baseUrl return _generateUri(path); - } else if (path.isEmpty && baseUrl.isEmpty) { + } + + if (path.isEmpty && baseUrl.isEmpty) { return _generateUri(''); - } else { - if (path.isNotEmpty && - baseUrl.isNotEmpty && - !baseUrl.endsWith('/') && - !path.startsWith('/')) { - return _generateUri('$baseUrl/$path'); - } + } - return _generateUri('$baseUrl$path'); + if (path.isNotEmpty && + baseUrl.isNotEmpty && + !baseUrl.endsWith('/') && + !path.startsWith('/')) { + return _generateUri('$baseUrl/$path'); } + + return _generateUri('$baseUrl$path'); } - Expression _generateUri(String url) => refer('Uri').newInstanceNamed( - 'parse', - [literal(url)], - ); + static Expression _generateUri(String url) => + refer('Uri').newInstanceNamed('parse', [literal(url)]); - Expression _generateRequest( + static Expression _generateRequest( ConstantReader method, { bool hasBody = false, bool hasParts = false, @@ -508,58 +519,47 @@ class ChopperGenerator extends GeneratorForAnnotation { bool useHeaders = false, bool useBrackets = false, bool includeNullQueryVars = false, - }) { - final List params = [ - literal(getMethodName(method)), - refer(_urlVar), - refer('$_clientVar.$_baseUrlVar'), - ]; - - final Map namedParams = {}; - - if (hasBody) { - namedParams['body'] = refer(_bodyVar); - } - - if (hasParts) { - namedParams['parts'] = refer(_partsVar); - namedParams['multipart'] = literalBool(true); - } - - if (useQueries) { - namedParams['parameters'] = refer(_parametersVar); - } - - if (useHeaders) { - namedParams['headers'] = refer(_headersVar); - } - - if (useBrackets) { - namedParams['useBrackets'] = literalBool(useBrackets); - } - - if (includeNullQueryVars) { - namedParams['includeNullQueryVars'] = literalBool(includeNullQueryVars); - } - - return refer('Request').newInstance(params, namedParams); - } - - Expression _generateMap(Map queries) { - final Map map = {}; - queries.forEach((ParameterElement p, ConstantReader r) { - final String name = r.peek('name')?.stringValue ?? p.displayName; - map[literal(name)] = refer(p.displayName); - }); + }) => + refer('Request').newInstance( + [ + literal(Utils.getMethodName(method)), + refer(Vars.url.toString()), + refer('${Vars.client}.${Vars.baseUrl}'), + ], + { + if (hasBody) 'body': refer(Vars.body.toString()), + if (hasParts) ...{ + 'parts': refer(Vars.parts.toString()), + 'multipart': literalBool(true), + }, + if (useQueries) 'parameters': refer(Vars.parameters.toString()), + if (useHeaders) 'headers': refer(Vars.headers.toString()), + if (useBrackets) 'useBrackets': literalBool(useBrackets), + if (includeNullQueryVars) + 'includeNullQueryVars': literalBool(includeNullQueryVars), + }, + ); - return literalMap(map, refer('String'), refer('dynamic')); - } + static Expression _generateMap( + Map queries, + ) => + literalMap( + { + for (final MapEntry query + in queries.entries) + query.value.peek('name')?.stringValue ?? query.key.displayName: + refer(query.key.displayName), + }, + refer('String'), + refer('dynamic'), + ); - Expression _generateList( + static Expression _generateList( Map parts, Map fileFields, ) { final List list = []; + parts.forEach((p, ConstantReader r) { final String name = r.peek('name')?.stringValue ?? p.displayName; final List params = [ @@ -573,6 +573,7 @@ class ChopperGenerator extends GeneratorForAnnotation { )}>', ).newInstance(params)); }); + fileFields.forEach((p, ConstantReader r) { final String name = r.peek('name')?.stringValue ?? p.displayName; final List params = [ @@ -591,10 +592,13 @@ class ChopperGenerator extends GeneratorForAnnotation { return literalList(list, refer('PartValue')); } - Code? _generateHeaders(MethodElement methodElement, ConstantReader method) { + static Code? _generateHeaders( + MethodElement methodElement, + ConstantReader method, + ) { final StringBuffer codeBuffer = StringBuffer('')..writeln('{'); - // Search for @Header anotation in method parameters + /// Search for @Header anotation in method parameters final Map annotations = _getAnnotations(methodElement, chopper.Header); @@ -628,81 +632,9 @@ class ChopperGenerator extends GeneratorForAnnotation { return code == '{\n}\n' ? null - : declareFinal(_headersVar, type: refer('Map')) - .assign(CodeExpression(Code(code))) - .statement; + : declareFinal( + Vars.headers.toString(), + type: refer('Map'), + ).assign(CodeExpression(Code(code))).statement; } } - -Builder chopperGeneratorFactoryBuilder(BuilderOptions options) => PartBuilder( - [ChopperGenerator()], - '.chopper.dart', - header: options.config['header'], - formatOutput: - PartBuilder([ChopperGenerator()], '.chopper.dart').formatOutput, - options: !options.config.containsKey('build_extensions') - ? options.overrideWith( - BuilderOptions({ - 'build_extensions': {'.dart': '.chopper.dart'}, - }), - ) - : options, - ); - -bool getMethodOptionalBody(ConstantReader method) => - method.read('optionalBody').boolValue; - -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; - -bool getIncludeNullQueryVars(ConstantReader method) => - method.peek('includeNullQueryVars')?.boolValue ?? false; - -extension DartTypeExtension on DartType { - bool get isNullable => nullabilitySuffix != NullabilitySuffix.none; -} - -// All positional required params must support nullability -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) => - 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!); - } - }); - -// Named params can be optional or required, they also need to support -// nullability -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!); - } - }); diff --git a/chopper_generator/lib/src/utils.dart b/chopper_generator/lib/src/utils.dart new file mode 100644 index 00000000..f1be6482 --- /dev/null +++ b/chopper_generator/lib/src/utils.dart @@ -0,0 +1,61 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:chopper_generator/src/extensions.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:source_gen/source_gen.dart'; + +class Utils { + static bool getMethodOptionalBody(ConstantReader method) => + method.read('optionalBody').boolValue; + + static String getMethodPath(ConstantReader method) => + method.read('path').stringValue; + + static String getMethodName(ConstantReader method) => + method.read('method').stringValue; + + static bool getUseBrackets(ConstantReader method) => + method.peek('useBrackets')?.boolValue ?? false; + + static bool getIncludeNullQueryVars(ConstantReader method) => + method.peek('includeNullQueryVars')?.boolValue ?? false; + + /// All positional required params must support nullability + static 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 + static 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!); + } + }); + + /// Named params can be optional or required, they also need to support nullability + static 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!); + } + }); +} diff --git a/chopper_generator/lib/src/vars.dart b/chopper_generator/lib/src/vars.dart new file mode 100644 index 00000000..cb7c5fdd --- /dev/null +++ b/chopper_generator/lib/src/vars.dart @@ -0,0 +1,17 @@ +enum Vars { + client('client'), + baseUrl('baseUrl'), + parameters(r'$params'), + headers(r'$headers'), + request(r'$request'), + body(r'$body'), + parts(r'$parts'), + url(r'$url'); + + const Vars(this.name); + + final String name; + + @override + String toString() => name; +} diff --git a/chopper_generator/mono_pkg.yaml b/chopper_generator/mono_pkg.yaml index 0620d98d..c0087871 100644 --- a/chopper_generator/mono_pkg.yaml +++ b/chopper_generator/mono_pkg.yaml @@ -6,6 +6,8 @@ stages: - group: - format - analyze: --fatal-infos . +- unit_test: + - test_with_coverage: cache: directories: diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index ee10e5de..8edb7a15 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -19,9 +19,12 @@ dependencies: source_gen: ^1.0.0 dev_dependencies: - test: ^1.16.4 + build_runner: ^2.0.0 + build_verify: ^3.1.0 dart_code_metrics: '>=4.8.1 <6.0.0' + http: ">=0.13.0 <2.0.0" lints: ^2.0.0 + test: ^1.16.4 dependency_overrides: # Comment before publish diff --git a/chopper_generator/test/ensure_build_test.dart b/chopper_generator/test/ensure_build_test.dart new file mode 100644 index 00000000..cb5dc60f --- /dev/null +++ b/chopper_generator/test/ensure_build_test.dart @@ -0,0 +1,16 @@ +@TestOn('vm') +@Timeout(Duration(seconds: 120)) +import 'package:build_verify/build_verify.dart'; +import 'package:test/test.dart'; + +void main() { + test( + 'ensure_build', + () => expectBuildClean( + packageRelativeDirectory: 'chopper_generator', + gitDiffPathArguments: [ + 'test/test_service.chopper.dart', + ], + ), + ); +} diff --git a/chopper_generator/test/test_service.chopper.dart b/chopper_generator/test/test_service.chopper.dart new file mode 100644 index 00000000..45e1d737 --- /dev/null +++ b/chopper_generator/test/test_service.chopper.dart @@ -0,0 +1,639 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_service.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// 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; + this.client = client; + } + + @override + final definitionType = HttpTestService; + + @override + Future> getTest( + String id, { + required String dynamicHeader, + }) { + final Uri $url = Uri.parse('/test/get/${id}'); + final Map $headers = { + 'test': dynamicHeader, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> headTest() { + final Uri $url = Uri.parse('/test/head'); + final Request $request = Request( + 'HEAD', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> optionsTest() { + final Uri $url = Uri.parse('/test/options'); + final Request $request = Request( + 'OPTIONS', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>>> getStreamTest() { + final Uri $url = Uri.parse('/test/get'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send>, int>($request); + } + + @override + Future> getAll() { + final Uri $url = Uri.parse('/test'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getAllWithTrailingSlash() { + final Uri $url = Uri.parse('/test/'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getQueryTest({ + String name = '', + int? number, + int? def = 42, + }) { + final Uri $url = Uri.parse('/test/query'); + final Map $params = { + 'name': name, + 'int': number, + 'default_value': def, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest(Map query) { + final Uri $url = Uri.parse('/test/query_map'); + final Map $params = query; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest2( + Map query, { + bool? test, + }) { + final Uri $url = Uri.parse('/test/query_map'); + final Map $params = {'test': test}; + $params.addAll(query); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest3({ + String name = '', + int? number, + Map filters = const {}, + }) { + final Uri $url = Uri.parse('/test/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest4({ + String name = '', + int? number, + Map? filters, + }) { + final Uri $url = Uri.parse('/test/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters ?? const {}); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest5({Map? filters}) { + final Uri $url = Uri.parse('/test/query_map'); + final Map $params = filters ?? const {}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getBody(dynamic body) { + final Uri $url = Uri.parse('/test/get_body'); + final $body = body; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postTest(String data) { + final Uri $url = Uri.parse('/test/post'); + final $body = data; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postStreamTest(Stream> byteStream) { + final Uri $url = Uri.parse('/test/post'); + final $body = byteStream; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> putTest( + String test, + String data, + ) { + final Uri $url = Uri.parse('/test/put/${test}'); + final $body = data; + final Request $request = Request( + 'PUT', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> deleteTest(String id) { + final Uri $url = Uri.parse('/test/delete/${id}'); + final Map $headers = { + 'foo': 'bar', + }; + final Request $request = Request( + 'DELETE', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> patchTest( + String id, + String data, + ) { + final Uri $url = Uri.parse('/test/patch/${id}'); + final $body = data; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> mapTest(Map map) { + final Uri $url = Uri.parse('/test/map'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postForm(Map fields) { + final Uri $url = Uri.parse('/test/form/body'); + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> postFormUsingHeaders(Map fields) { + final Uri $url = Uri.parse('/test/form/body'); + final Map $headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> postFormFields( + String foo, + int bar, + ) { + final Uri $url = Uri.parse('/test/form/body/fields'); + final $body = { + 'foo': foo, + 'bar': bar, + }; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> forceJsonTest(Map map) { + final Uri $url = Uri.parse('/test/map/json'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: customConvertRequest, + responseConverter: customConvertResponse, + ); + } + + @override + Future> postResources( + Map a, + Map b, + ) { + final Uri $url = Uri.parse('/test/multi'); + final List $parts = [ + PartValue>( + '1', + a, + ), + PartValue>( + '2', + b, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postFile(List bytes) { + final Uri $url = Uri.parse('/test/file'); + final List $parts = [ + PartValueFile>( + 'file', + bytes, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + 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, { + String? id, + }) { + final Uri $url = Uri.parse('/test/file'); + final List $parts = [ + PartValue( + 'id', + id, + ), + PartValueFile( + 'file', + file, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postListFiles(List files) { + final Uri $url = Uri.parse('/test/files'); + final List $parts = [ + PartValueFile>( + 'files', + files, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postMultipartList({ + required List ints, + required List doubles, + required List nums, + required List strings, + }) { + final Uri $url = Uri.parse('/test/multipart_list'); + final List $parts = [ + PartValue>( + 'ints', + ints, + ), + PartValue>( + 'doubles', + doubles, + ), + PartValue>( + 'nums', + nums, + ), + PartValue>( + 'strings', + strings, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future fullUrl() { + final Uri $url = Uri.parse('https://test.com'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>> listString() { + final Uri $url = Uri.parse('/test/list/string'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send, String>($request); + } + + @override + Future> noBody() { + final Uri $url = Uri.parse('/test/no-body'); + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getUsingQueryParamIncludeNullQueryVars({ + String? foo, + String? bar, + String? baz, + }) { + final Uri $url = Uri.parse('/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 Uri $url = Uri.parse('/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 Uri $url = Uri.parse('/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 Uri $url = Uri.parse('/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> getUsingMapQueryParamIncludeNullQueryVars( + Map value) { + final Uri $url = Uri.parse('/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) { + final Uri $url = Uri.parse('/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_generator/test/test_service.dart b/chopper_generator/test/test_service.dart new file mode 100644 index 00000000..c0baf1a4 --- /dev/null +++ b/chopper_generator/test/test_service.dart @@ -0,0 +1,218 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' show MultipartFile; + +part 'test_service.chopper.dart'; + +@ChopperApi(baseUrl: '/test') +abstract class HttpTestService extends ChopperService { + static HttpTestService create([ChopperClient? client]) => + _$HttpTestService(client); + + @Get(path: 'get/{id}') + Future> getTest( + @Path() String id, { + @Header('test') required String dynamicHeader, + }); + + @Head(path: 'head') + Future headTest(); + + @Options(path: 'options') + Future optionsTest(); + + @Get(path: 'get') + Future>>> getStreamTest(); + + @Get(path: '') + Future getAll(); + + @Get(path: '/') + Future getAllWithTrailingSlash(); + + @Get(path: 'query') + Future getQueryTest({ + @Query('name') String name = '', + @Query('int') int? number, + @Query('default_value') int? def = 42, + }); + + @Get(path: 'query_map') + Future getQueryMapTest(@QueryMap() Map query); + + @Get(path: 'query_map') + Future getQueryMapTest2( + @QueryMap() Map query, { + @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); + + @Post(path: 'post') + Future postTest(@Body() String data); + + @Post(path: 'post') + Future postStreamTest(@Body() Stream> byteStream); + + @Put(path: 'put/{id}') + Future putTest(@Path('id') String test, @Body() String data); + + @Delete(path: 'delete/{id}', headers: {'foo': 'bar'}) + Future deleteTest(@Path() String id); + + @Patch(path: 'patch/{id}') + Future patchTest(@Path() String id, @Body() String data); + + @Post(path: 'map') + Future mapTest(@Body() Map map); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body') + Future postForm(@Body() Map fields); + + @Post(path: 'form/body', headers: {contentTypeKey: formEncodedHeaders}) + Future postFormUsingHeaders(@Body() Map fields); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body/fields') + Future postFormFields(@Field() String foo, @Field() int bar); + + @Post(path: 'map/json') + @FactoryConverter( + request: customConvertRequest, + response: customConvertResponse, + ) + Future forceJsonTest(@Body() Map map); + + @Post(path: 'multi') + @multipart + Future postResources( + @Part('1') Map a, + @Part('2') Map b, + ); + + @Post(path: 'file') + @multipart + Future postFile( + @PartFile('file') List bytes, + ); + + @Post(path: 'image') + @multipart + Future postImage( + @PartFile('image') List imageData, + ); + + @Post(path: 'file') + @multipart + Future postMultipartFile( + @PartFile() MultipartFile file, { + @Part() String? id, + }); + + @Post(path: 'files') + @multipart + Future postListFiles(@PartFile() List files); + + @Post(path: 'multipart_list') + @multipart + Future postMultipartList({ + @Part('ints') required List ints, + @Part('doubles') required List doubles, + @Part('nums') required List nums, + @Part('strings') required List strings, + }); + + @Get(path: 'https://test.com') + Future fullUrl(); + + @Get(path: '/list/string') + Future>> listString(); + + @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, + ); + + @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_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, + ); +} + +Request customConvertRequest(Request req) { + final r = JsonConverter().convertRequest(req); + + return applyHeader(r, 'customConverter', 'true'); +} + +Response customConvertResponse(Response res) => + res.copyWith(body: json.decode(res.body)); + +Request convertForm(Request req) { + req = applyHeader(req, contentTypeKey, formEncodedHeaders); + + if (req.body is Map) { + final body = {}; + + req.body.forEach((key, val) { + if (val != null) { + body[key.toString()] = val.toString(); + } + }); + + req = req.copyWith(body: body); + } + + return req; +} From 0856b86244a5343e159000af7a03184a1ca1172e Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 5 Jul 2023 10:52:15 +0100 Subject: [PATCH 073/102] :twisted_rightwards_arrows: post release branch sync (#450) --- chopper_generator/CHANGELOG.md | 13 +++++++++++++ chopper_generator/pubspec.yaml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 881ab59e..36c3e7ca 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 6.0.3 + +- Simplify library export +- Extract PartBuilder into its own file +- Extract constant variables into an enum +- Extract helper methods into a Utils class +- Use a const constructor +- Make all methods static +- Ensure all immutable variables are final +- Simplify syntax +- Add API documentation +- Update README + ## 6.0.2 - Add support for generating files in different directories ([#444](https://github.com/lejard-h/chopper/pull/444)) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 8edb7a15..fc438eca 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: 6.0.2 +version: 6.0.3 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 96f9dbc7baaf38cf4cc0e59b860954274ff09850 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 9 Jul 2023 12:34:47 +0100 Subject: [PATCH 074/102] :heavy_minus_sign: remove dart_code_metrics from dev_dependencies (#452) * :heavy_minus_sign: remove dart_code_metrics as a dev dependency * :technologist: update examples --- chopper/analysis_options.yaml | 21 -- chopper/pubspec.yaml | 1 - chopper_built_value/analysis_options.yaml | 21 -- chopper_built_value/pubspec.yaml | 1 - chopper_generator/analysis_options.yaml | 22 -- chopper_generator/pubspec.yaml | 1 - example/analysis_options.yaml | 20 -- .../lib/json_decode_service.activator.g.dart | 3 +- example/lib/json_decode_service.dart | 3 +- example/lib/json_decode_service.vm.g.dart | 12 +- example/lib/json_decode_service.worker.g.dart | 207 ++++++++++-------- example/pubspec.yaml | 21 +- 12 files changed, 132 insertions(+), 201 deletions(-) diff --git a/chopper/analysis_options.yaml b/chopper/analysis_options.yaml index 7f5a674f..6f56a451 100644 --- a/chopper/analysis_options.yaml +++ b/chopper/analysis_options.yaml @@ -6,27 +6,6 @@ analyzer: - "**.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: diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 2af5cfbf..ba990c81 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -19,7 +19,6 @@ dev_dependencies: build_verify: ^3.1.0 collection: ^1.16.0 coverage: ^1.0.2 - dart_code_metrics: '>=4.8.1 <6.0.0' data_fixture_dart: ^2.2.0 faker: ^2.1.0 http_parser: ^4.0.0 diff --git a/chopper_built_value/analysis_options.yaml b/chopper_built_value/analysis_options.yaml index 7f5a674f..6f56a451 100644 --- a/chopper_built_value/analysis_options.yaml +++ b/chopper_built_value/analysis_options.yaml @@ -6,27 +6,6 @@ analyzer: - "**.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: diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 1eed9725..65c3ab03 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -18,7 +18,6 @@ dev_dependencies: build_runner: ^2.0.0 build_test: ^2.0.0 built_value_generator: ^8.0.6 - dart_code_metrics: '>=4.8.1 <6.0.0' lints: ^2.0.0 dependency_overrides: diff --git a/chopper_generator/analysis_options.yaml b/chopper_generator/analysis_options.yaml index 2caa0f09..6f56a451 100644 --- a/chopper_generator/analysis_options.yaml +++ b/chopper_generator/analysis_options.yaml @@ -6,28 +6,6 @@ analyzer: - "**.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: 10 - source-lines-of-code: 250 - 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: diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index fc438eca..645318cd 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -21,7 +21,6 @@ dependencies: dev_dependencies: build_runner: ^2.0.0 build_verify: ^3.1.0 - dart_code_metrics: '>=4.8.1 <6.0.0' http: ">=0.13.0 <2.0.0" lints: ^2.0.0 test: ^1.16.4 diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 7061686f..8ed20e77 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -5,26 +5,6 @@ analyzer: - "**.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: diff --git a/example/lib/json_decode_service.activator.g.dart b/example/lib/json_decode_service.activator.g.dart index 1e151e3c..48ed6298 100644 --- a/example/lib/json_decode_service.activator.g.dart +++ b/example/lib/json_decode_service.activator.g.dart @@ -1,9 +1,10 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** -// Generated by: WorkerGenerator +// Generator: WorkerGenerator 2.4.1 // ************************************************************************** import 'json_decode_service.vm.g.dart'; +/// Service activator for JsonDecodeService final $JsonDecodeServiceActivator = $getJsonDecodeServiceActivator(); diff --git a/example/lib/json_decode_service.dart b/example/lib/json_decode_service.dart index 41c94785..299b73c3 100644 --- a/example/lib/json_decode_service.dart +++ b/example/lib/json_decode_service.dart @@ -14,8 +14,7 @@ part 'json_decode_service.worker.g.dart'; // disable web to keep the number of generated files low for this example web: false, ) -class JsonDecodeService extends WorkerService - with $JsonDecodeServiceOperations { +class JsonDecodeService { @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 index 656bcef1..09d73096 100644 --- a/example/lib/json_decode_service.vm.g.dart +++ b/example/lib/json_decode_service.vm.g.dart @@ -1,13 +1,15 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** -// Generated by: WorkerGenerator +// Generator: WorkerGenerator 2.4.1 // ************************************************************************** -import 'package:squadron/squadron_service.dart'; +import 'package:squadron/squadron.dart'; + import 'json_decode_service.dart'; -// VM entry point -void _start(List command) => run($JsonDecodeServiceInitializer, command, null); +/// VM entry point for JsonDecodeService +void _start$JsonDecodeService(List command) => + run($JsonDecodeServiceInitializer, command, null); -dynamic $getJsonDecodeServiceActivator() => _start; +EntryPoint $getJsonDecodeServiceActivator() => _start$JsonDecodeService; diff --git a/example/lib/json_decode_service.worker.g.dart b/example/lib/json_decode_service.worker.g.dart index 0cd306ce..18a33d72 100644 --- a/example/lib/json_decode_service.worker.g.dart +++ b/example/lib/json_decode_service.worker.g.dart @@ -3,53 +3,64 @@ part of 'json_decode_service.dart'; // ************************************************************************** -// WorkerGenerator +// Generator: WorkerGenerator 2.4.1 // ************************************************************************** -// Operations map for JsonDecodeService -mixin $JsonDecodeServiceOperations on WorkerService { +/// WorkerService class for JsonDecodeService +class _$JsonDecodeServiceWorkerService extends JsonDecodeService + implements WorkerService { + _$JsonDecodeServiceWorkerService() : super(); + @override - late final Map operations = - _getOperations(this as JsonDecodeService); + Map get operations => _operations; - static const int _$jsonDecodeId = 1; + late final Map _operations = { + _$jsonDecodeId: ($) => jsonDecode($.args[0]) + }; - static Map _getOperations(JsonDecodeService svc) => - {_$jsonDecodeId: (req) => svc.jsonDecode(req.args[0])}; + static const int _$jsonDecodeId = 1; } -// Service initializer -JsonDecodeService $JsonDecodeServiceInitializer(WorkerRequest startRequest) => - JsonDecodeService(); - -// Worker for JsonDecodeService -class _JsonDecodeServiceWorker extends Worker - with $JsonDecodeServiceOperations - implements JsonDecodeService { - _JsonDecodeServiceWorker() : super($JsonDecodeServiceActivator); +/// Service initializer for JsonDecodeService +WorkerService $JsonDecodeServiceInitializer(WorkerRequest startRequest) => + _$JsonDecodeServiceWorkerService(); +/// Operations map for JsonDecodeService +@Deprecated( + 'squadron_builder now supports "plain old Dart objects" as services. ' + 'Services do not need to derive from WorkerService nor do they need to mix in ' + 'with \$JsonDecodeServiceOperations anymore.') +mixin $JsonDecodeServiceOperations on WorkerService { @override - Future jsonDecode(String source) => send( - $JsonDecodeServiceOperations._$jsonDecodeId, - args: [source], - ); + // not needed anymore, generated for compatibility with previous versions of squadron_builder + Map get operations => WorkerService.noOperations; +} + +/// Worker for JsonDecodeService +class _$JsonDecodeServiceWorker extends Worker implements JsonDecodeService { + _$JsonDecodeServiceWorker({PlatformWorkerHook? platformWorkerHook}) + : super($JsonDecodeServiceActivator, + platformWorkerHook: platformWorkerHook); @override - Map get operations => WorkerService.noOperations; + Future jsonDecode(String source) => + send(_$JsonDecodeServiceWorkerService._$jsonDecodeId, args: [source]); final Object _detachToken = Object(); } -// Finalizable worker wrapper for JsonDecodeService -class JsonDecodeServiceWorker implements _JsonDecodeServiceWorker { - JsonDecodeServiceWorker() : _worker = _JsonDecodeServiceWorker() { - _finalizer.attach(this, _worker, detach: _worker._detachToken); +/// Finalizable worker wrapper for JsonDecodeService +class JsonDecodeServiceWorker implements _$JsonDecodeServiceWorker { + JsonDecodeServiceWorker({PlatformWorkerHook? platformWorkerHook}) + : _$w = + _$JsonDecodeServiceWorker(platformWorkerHook: platformWorkerHook) { + _finalizer.attach(this, _$w, detach: _$w._detachToken); } - final _JsonDecodeServiceWorker _worker; + final _$JsonDecodeServiceWorker _$w; - static final Finalizer<_JsonDecodeServiceWorker> _finalizer = - Finalizer<_JsonDecodeServiceWorker>((w) { + static final Finalizer<_$JsonDecodeServiceWorker> _finalizer = + Finalizer<_$JsonDecodeServiceWorker>((w) { try { _finalizer.detach(w._detachToken); w.stop(); @@ -59,52 +70,52 @@ class JsonDecodeServiceWorker implements _JsonDecodeServiceWorker { }); @override - Future jsonDecode(String source) => _worker.jsonDecode(source); + Future jsonDecode(String source) => _$w.jsonDecode(source); @override - Map get operations => _worker.operations; + List get args => _$w.args; @override - List get args => _worker.args; + Channel? get channel => _$w.channel; @override - Channel? get channel => _worker.channel; + Duration get idleTime => _$w.idleTime; @override - Duration get idleTime => _worker.idleTime; + bool get isStopped => _$w.isStopped; @override - bool get isStopped => _worker.isStopped; + int get maxWorkload => _$w.maxWorkload; @override - int get maxWorkload => _worker.maxWorkload; + WorkerStat get stats => _$w.stats; @override - WorkerStat get stats => _worker.stats; + String get status => _$w.status; @override - String get status => _worker.status; + int get totalErrors => _$w.totalErrors; @override - int get totalErrors => _worker.totalErrors; + int get totalWorkload => _$w.totalWorkload; @override - int get totalWorkload => _worker.totalWorkload; + Duration get upTime => _$w.upTime; @override - Duration get upTime => _worker.upTime; + String get workerId => _$w.workerId; @override - String get workerId => _worker.workerId; + int get workload => _$w.workload; @override - int get workload => _worker.workload; + PlatformWorkerHook? get platformWorkerHook => _$w.platformWorkerHook; @override - Future start() => _worker.start(); + Future start() => _$w.start(); @override - void stop() => _worker.stop(); + void stop() => _$w.stop(); @override Future send(int command, @@ -112,7 +123,7 @@ class JsonDecodeServiceWorker implements _JsonDecodeServiceWorker { CancellationToken? token, bool inspectRequest = false, bool inspectResponse = false}) => - _worker.send(command, + _$w.send(command, args: args, token: token, inspectRequest: inspectRequest, @@ -124,46 +135,52 @@ class JsonDecodeServiceWorker implements _JsonDecodeServiceWorker { CancellationToken? token, bool inspectRequest = false, bool inspectResponse = false}) => - _worker.stream(command, + _$w.stream(command, args: args, token: token, inspectRequest: inspectRequest, inspectResponse: inspectResponse); @override - Object get _detachToken => _worker._detachToken; + Object get _detachToken => _$w._detachToken; + + @override + Map get operations => WorkerService.noOperations; } -// Worker pool for JsonDecodeService -class _JsonDecodeServiceWorkerPool extends WorkerPool - with $JsonDecodeServiceOperations +/// Worker pool for JsonDecodeService +class _$JsonDecodeServiceWorkerPool extends WorkerPool implements JsonDecodeService { - _JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) - : super(() => JsonDecodeServiceWorker(), + _$JsonDecodeServiceWorkerPool( + {ConcurrencySettings? concurrencySettings, + PlatformWorkerHook? platformWorkerHook}) + : super( + () => + JsonDecodeServiceWorker(platformWorkerHook: platformWorkerHook), concurrencySettings: concurrencySettings); @override Future jsonDecode(String source) => - execute(($w) => $w.jsonDecode(source)); - - @override - Map get operations => WorkerService.noOperations; + execute((w) => w.jsonDecode(source)); final Object _detachToken = Object(); } -// Finalizable worker pool wrapper for JsonDecodeService -class JsonDecodeServiceWorkerPool implements _JsonDecodeServiceWorkerPool { - JsonDecodeServiceWorkerPool({ConcurrencySettings? concurrencySettings}) - : _pool = _JsonDecodeServiceWorkerPool( - concurrencySettings: concurrencySettings) { - _finalizer.attach(this, _pool, detach: _pool._detachToken); +/// Finalizable worker pool wrapper for JsonDecodeService +class JsonDecodeServiceWorkerPool implements _$JsonDecodeServiceWorkerPool { + JsonDecodeServiceWorkerPool( + {ConcurrencySettings? concurrencySettings, + PlatformWorkerHook? platformWorkerHook}) + : _$p = _$JsonDecodeServiceWorkerPool( + concurrencySettings: concurrencySettings, + platformWorkerHook: platformWorkerHook) { + _finalizer.attach(this, _$p, detach: _$p._detachToken); } - final _JsonDecodeServiceWorkerPool _pool; + final _$JsonDecodeServiceWorkerPool _$p; - static final Finalizer<_JsonDecodeServiceWorkerPool> _finalizer = - Finalizer<_JsonDecodeServiceWorkerPool>((p) { + static final Finalizer<_$JsonDecodeServiceWorkerPool> _finalizer = + Finalizer<_$JsonDecodeServiceWorkerPool>((p) { try { _finalizer.detach(p._detachToken); p.stop(); @@ -173,101 +190,101 @@ class JsonDecodeServiceWorkerPool implements _JsonDecodeServiceWorkerPool { }); @override - Future jsonDecode(String source) => _pool.jsonDecode(source); + Future jsonDecode(String source) => _$p.jsonDecode(source); @override - Map get operations => _pool.operations; + ConcurrencySettings get concurrencySettings => _$p.concurrencySettings; @override - ConcurrencySettings get concurrencySettings => _pool.concurrencySettings; + Iterable get fullStats => _$p.fullStats; @override - Iterable get fullStats => _pool.fullStats; + int get maxConcurrency => _$p.maxConcurrency; @override - int get maxConcurrency => _pool.maxConcurrency; + int get maxParallel => _$p.maxParallel; @override - int get maxParallel => _pool.maxParallel; + int get maxSize => _$p.maxSize; @override - int get maxSize => _pool.maxSize; + int get maxWorkers => _$p.maxWorkers; @override - int get maxWorkers => _pool.maxWorkers; + int get maxWorkload => _$p.maxWorkload; @override - int get maxWorkload => _pool.maxWorkload; + int get minWorkers => _$p.minWorkers; @override - int get minWorkers => _pool.minWorkers; + int get pendingWorkload => _$p.pendingWorkload; @override - int get pendingWorkload => _pool.pendingWorkload; + int get size => _$p.size; @override - int get size => _pool.size; + Iterable get stats => _$p.stats; @override - Iterable get stats => _pool.stats; + bool get stopped => _$p.stopped; @override - bool get stopped => _pool.stopped; + int get totalErrors => _$p.totalErrors; @override - int get totalErrors => _pool.totalErrors; + int get totalWorkload => _$p.totalWorkload; @override - int get totalWorkload => _pool.totalWorkload; + int get workload => _$p.workload; @override - int get workload => _pool.workload; + void cancel([Task? task, String? message]) => _$p.cancel(task, message); @override - void cancel([Task? task, String? message]) => _pool.cancel(task, message); - - @override - FutureOr start() => _pool.start(); + FutureOr start() => _$p.start(); @override int stop([bool Function(JsonDecodeServiceWorker worker)? predicate]) => - _pool.stop(predicate); + _$p.stop(predicate); @override Object registerWorkerPoolListener( void Function(JsonDecodeServiceWorker worker, bool removed) listener) => - _pool.registerWorkerPoolListener(listener); + _$p.registerWorkerPoolListener(listener); @override void unregisterWorkerPoolListener( {void Function(JsonDecodeServiceWorker worker, bool removed)? listener, Object? token}) => - _pool.unregisterWorkerPoolListener(listener: listener, token: token); + _$p.unregisterWorkerPoolListener(listener: listener, token: token); @override Future execute(Future Function(JsonDecodeServiceWorker worker) task, {PerfCounter? counter}) => - _pool.execute(task, counter: counter); + _$p.execute(task, counter: counter); @override StreamTask scheduleStream( Stream Function(JsonDecodeServiceWorker worker) task, {PerfCounter? counter}) => - _pool.scheduleStream(task, counter: counter); + _$p.scheduleStream(task, counter: counter); @override ValueTask scheduleTask( Future Function(JsonDecodeServiceWorker worker) task, {PerfCounter? counter}) => - _pool.scheduleTask(task, counter: counter); + _$p.scheduleTask(task, counter: counter); @override Stream stream(Stream Function(JsonDecodeServiceWorker worker) task, {PerfCounter? counter}) => - _pool.stream(task, counter: counter); + _$p.stream(task, counter: counter); @override - Object get _detachToken => _pool._detachToken; + Object get _detachToken => _$p._detachToken; + + @override + Map get operations => WorkerService.noOperations; } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d88b0adf..18b93a17 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -9,21 +9,20 @@ environment: dependencies: chopper: - json_annotation: + json_annotation: ^4.8.1 built_value: - analyzer: - http: - built_collection: - squadron: ^5.0.0 + analyzer: ^5.13.0 + http: ^1.1.0 + built_collection: ^5.1.1 + squadron: ^5.1.3 dev_dependencies: - build_runner: + build_runner: ^2.4.6 chopper_generator: - json_serializable: - built_value_generator: - dart_code_metrics: '>=4.8.1 <6.0.0' - lints: ^2.0.0 - squadron_builder: ^2.1.2 + json_serializable: ^6.7.1 + built_value_generator: ^8.6.1 + lints: ^2.1.1 + squadron_builder: ^2.4.1 dependency_overrides: chopper: From 6afebb0f6712698c60b9f0aa8016af9c99f31275 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 29 Jul 2023 17:16:56 +0100 Subject: [PATCH 075/102] :sparkles: implement Dart 3 class modifiers (#453) * bump minimum Dart SDK version to 3.0.0 * bump dependencies to the latest (except analyzer) * implement Dart 3 class modifiers --- chopper/lib/src/annotations.dart | 44 +++++++++---------- chopper/lib/src/base.dart | 4 +- chopper/lib/src/constants.dart | 2 +- chopper/lib/src/interceptor.dart | 8 ++-- chopper/lib/src/request.dart | 6 +-- chopper/lib/src/response.dart | 2 +- chopper/lib/src/utils.dart | 2 +- chopper/pubspec.yaml | 26 +++++------ .../test/fixtures/http_response_fixture.dart | 2 +- chopper/test/fixtures/payload_fixture.dart | 2 +- chopper/test/fixtures/request_fixture.dart | 2 +- chopper/test/fixtures/response_fixture.dart | 2 +- chopper/test/helpers/payload.dart | 2 +- chopper_built_value/pubspec.yaml | 22 +++++----- chopper_generator/lib/src/generator.dart | 9 ++-- chopper_generator/lib/src/utils.dart | 2 +- chopper_generator/pubspec.yaml | 30 ++++++------- example/pubspec.yaml | 2 +- 18 files changed, 84 insertions(+), 85 deletions(-) diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index e7fa787c..9030d432 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -20,7 +20,7 @@ import 'package:meta/meta.dart'; /// /// See [Method] to define an HTTP request @immutable -class ChopperApi { +final class ChopperApi { /// A part of a URL that every request defined inside a class annotated with [ChopperApi] will be prefixed with. final String baseUrl; @@ -42,7 +42,7 @@ class ChopperApi { /// Future fetch(@Path() String param); /// ``` @immutable -class Path { +final class Path { /// Name is used to bind a method parameter to /// a URL path parameter. /// ```dart @@ -65,7 +65,7 @@ class Path { /// /// See [QueryMap] to pass an [Map] as value @immutable -class Query { +final class Query { /// Name is used to bind a method parameter to /// the query parameter. /// ```dart @@ -90,7 +90,7 @@ class Query { /// // something?foo=bar&list=1&list=2 /// ``` @immutable -class QueryMap { +final class QueryMap { const QueryMap(); } @@ -104,7 +104,7 @@ class QueryMap { /// The body can be of any type, but chopper does not automatically convert it to JSON. /// See [Converter] to apply conversion to the body. @immutable -class Body { +final class Body { const Body(); } @@ -117,7 +117,7 @@ class Body { /// Future fetch(@Header() String foo); /// ``` @immutable -class Header { +final class Header { /// Name is used to bind a method parameter to /// a header name. /// ```dart @@ -147,7 +147,7 @@ class Header { /// However, chopper will not automatically convert the body response to your type. /// A [Converter] needs to be specified for conversion. @immutable -class Method { +sealed class Method { /// HTTP method for the request final String method; @@ -207,7 +207,7 @@ class Method { /// Defines a method as an HTTP GET request. @immutable -class Get extends Method { +final class Get extends Method { const Get({ super.optionalBody = true, super.path, @@ -221,7 +221,7 @@ class Get extends Method { /// /// Use the [Body] annotation to pass data to send. @immutable -class Post extends Method { +final class Post extends Method { const Post({ super.optionalBody, super.path, @@ -233,7 +233,7 @@ class Post extends Method { /// Defines a method as an HTTP DELETE request. @immutable -class Delete extends Method { +final class Delete extends Method { const Delete({ super.optionalBody = true, super.path, @@ -247,7 +247,7 @@ class Delete extends Method { /// /// Use the [Body] annotation to pass data to send. @immutable -class Put extends Method { +final class Put extends Method { const Put({ super.optionalBody, super.path, @@ -260,7 +260,7 @@ class Put extends Method { /// Defines a method as an HTTP PATCH request. /// Use the [Body] annotation to pass data to send. @immutable -class Patch extends Method { +final class Patch extends Method { const Patch({ super.optionalBody, super.path, @@ -272,7 +272,7 @@ class Patch extends Method { /// Defines a method as an HTTP HEAD request. @immutable -class Head extends Method { +final class Head extends Method { const Head({ super.optionalBody = true, super.path, @@ -283,7 +283,7 @@ class Head extends Method { } @immutable -class Options extends Method { +final class Options extends Method { const Options({ super.optionalBody = true, super.path, @@ -330,7 +330,7 @@ typedef ConvertResponse = FutureOr Function(Response response); /// } /// ``` @immutable -class FactoryConverter { +final class FactoryConverter { final ConvertRequest? request; final ConvertResponse? response; @@ -349,7 +349,7 @@ class FactoryConverter { /// ``` /// Will be converted to `{ 'name': value }`. @immutable -class Field { +final class Field { /// Name can be use to specify the name of the field /// ```dart /// @Post(path: '/') @@ -367,7 +367,7 @@ class Field { /// Future fetch(@FieldMap List> query); /// ``` @immutable -class FieldMap { +final class FieldMap { const FieldMap(); } @@ -382,7 +382,7 @@ class FieldMap { /// Use [Part] annotation to send simple data. /// Use [PartFile] annotation to send `File` or `List`. @immutable -class Multipart { +final class Multipart { const Multipart(); } @@ -392,7 +392,7 @@ class Multipart { /// /// Also accepts `MultipartFile` (from package:http). @immutable -class Part { +final class Part { final String? name; const Part([this.name]); @@ -406,7 +406,7 @@ class Part { /// Future fetch(@PartMap() List query); /// ``` @immutable -class PartMap { +final class PartMap { const PartMap(); } @@ -423,7 +423,7 @@ class PartMap { /// - [String] (path of your file) /// - `MultipartFile` (from package:http) @immutable -class PartFile { +final class PartFile { final String? name; const PartFile([this.name]); @@ -437,7 +437,7 @@ class PartFile { /// Future fetch(@PartFileMap() List query); /// ``` @immutable -class PartFileMap { +final class PartFileMap { const PartFileMap(); } diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 4fa556c2..27ab57fc 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -13,7 +13,7 @@ import 'package:meta/meta.dart'; Type _typeOf() => T; @visibleForTesting -final List allowedInterceptorsType = [ +const List allowedInterceptorsType = [ RequestInterceptor, RequestInterceptorFunc, ResponseInterceptor, @@ -26,7 +26,7 @@ final List allowedInterceptorsType = [ /// /// It manages registered services, encodes and decodes data, and intercepts /// requests and responses. -class ChopperClient { +base class ChopperClient { /// Base URL of each request of the registered services. /// E.g., the hostname of your service. final Uri baseUrl; diff --git a/chopper/lib/src/constants.dart b/chopper/lib/src/constants.dart index 52db96c8..e1c9d7f1 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'; -abstract class HttpMethod { +abstract final class HttpMethod { static const String Get = 'GET'; static const String Post = 'POST'; static const String Put = 'PUT'; diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index b8595cea..185bfc97 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -31,7 +31,7 @@ import 'package:meta/meta.dart'; /// } /// ``` @immutable -abstract class ResponseInterceptor { +abstract interface class ResponseInterceptor { FutureOr onResponse(Response response); } @@ -58,7 +58,7 @@ abstract class ResponseInterceptor { /// /// (See [applyHeader(request, name, value)] and [applyHeaders(request, headers)].) @immutable -abstract class RequestInterceptor { +abstract interface class RequestInterceptor { FutureOr onRequest(Request request); } @@ -72,7 +72,7 @@ abstract class RequestInterceptor { /// /// See [JsonConverter] and [FormUrlEncodedConverter] for example implementations. @immutable -abstract class Converter { +abstract interface class Converter { /// Converts the received [Request] to a [Request] which has a body with the /// HTTP representation of the original body. FutureOr convertRequest(Request request); @@ -94,7 +94,7 @@ abstract class Converter { /// /// An `ErrorConverter` is called only on error responses /// (statusCode < 200 || statusCode >= 300) and before any [ResponseInterceptor]s. -abstract class ErrorConverter { +abstract interface class ErrorConverter { /// Converts the received [Response] to a [Response] which has a body with the /// HTTP representation of the original body. FutureOr convertError(Response response); diff --git a/chopper/lib/src/request.dart b/chopper/lib/src/request.dart index 736126ee..1441ef3d 100644 --- a/chopper/lib/src/request.dart +++ b/chopper/lib/src/request.dart @@ -7,7 +7,7 @@ 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 with EquatableMixin { +base class Request extends http.BaseRequest with EquatableMixin { final Uri uri; final Uri baseUri; final dynamic body; @@ -232,7 +232,7 @@ class Request extends http.BaseRequest with EquatableMixin { /// Represents a part in a multipart request. @immutable -class PartValue with EquatableMixin { +final class PartValue with EquatableMixin { final T value; final String name; @@ -258,6 +258,6 @@ class PartValue with EquatableMixin { /// Represents a file [PartValue] in a multipart request. @immutable -class PartValueFile extends PartValue { +final class PartValueFile extends PartValue { const PartValueFile(super.name, super.value); } diff --git a/chopper/lib/src/response.dart b/chopper/lib/src/response.dart index 8f44e1c5..3ec5c499 100644 --- a/chopper/lib/src/response.dart +++ b/chopper/lib/src/response.dart @@ -16,7 +16,7 @@ import 'package:meta/meta.dart'; /// Future> fetchItem(); /// ``` @immutable -class Response with EquatableMixin { +base class Response with EquatableMixin { /// The [http.BaseResponse] from `package:http` that this [Response] wraps. final http.BaseResponse base; diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index c8853170..ce78291c 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -131,7 +131,7 @@ Iterable<_Pair> _iterableToQuery( String _normalizeValue(value) => Uri.encodeComponent(value?.toString() ?? ''); -class _Pair with EquatableMixin { +final class _Pair with EquatableMixin { final A first; final B second; final bool useBrackets; diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index ba990c81..5a6d5859 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -1,29 +1,29 @@ name: chopper description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 6.1.4 +version: 7.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: equatable: ^2.0.5 - http: ">=0.13.0 <2.0.0" - logging: ^1.0.0 - meta: ^1.3.0 + http: ^1.1.0 + logging: ^1.2.0 + meta: ^1.9.1 dev_dependencies: - build_runner: ^2.0.0 - build_test: ^2.0.0 + build_runner: ^2.4.6 + build_test: ^2.2.0 build_verify: ^3.1.0 - collection: ^1.16.0 - coverage: ^1.0.2 + collection: ^1.18.0 + coverage: ^1.6.3 data_fixture_dart: ^2.2.0 faker: ^2.1.0 - http_parser: ^4.0.0 - lints: ^2.0.0 - test: ^1.16.4 - transparent_image: ^2.0.0 + http_parser: ^4.0.2 + lints: ^2.1.1 + test: ^1.24.4 + transparent_image: ^2.0.1 chopper_generator: path: ../chopper_generator diff --git a/chopper/test/fixtures/http_response_fixture.dart b/chopper/test/fixtures/http_response_fixture.dart index 6b6b6071..96292abd 100644 --- a/chopper/test/fixtures/http_response_fixture.dart +++ b/chopper/test/fixtures/http_response_fixture.dart @@ -12,7 +12,7 @@ extension ResponseFixture on http.Response { } @internal -class ResponseFactory extends FixtureFactory { +final class ResponseFactory extends FixtureFactory { @override FixtureDefinition definition() => define( (Faker faker) => http.Response( diff --git a/chopper/test/fixtures/payload_fixture.dart b/chopper/test/fixtures/payload_fixture.dart index 435883cf..c7a5fb24 100644 --- a/chopper/test/fixtures/payload_fixture.dart +++ b/chopper/test/fixtures/payload_fixture.dart @@ -8,7 +8,7 @@ extension PayloadFixture on Payload { } @internal -class PayloadFactory extends FixtureFactory { +final class PayloadFactory extends FixtureFactory { @override FixtureDefinition definition() => define( (Faker faker) => Payload( diff --git a/chopper/test/fixtures/request_fixture.dart b/chopper/test/fixtures/request_fixture.dart index 6d422cf4..073488ef 100644 --- a/chopper/test/fixtures/request_fixture.dart +++ b/chopper/test/fixtures/request_fixture.dart @@ -9,7 +9,7 @@ extension RequestFixture on Request { } @internal -class RequestFixtureFactory extends FixtureFactory { +final class RequestFixtureFactory extends FixtureFactory { @override FixtureDefinition definition() { final String method = diff --git a/chopper/test/fixtures/response_fixture.dart b/chopper/test/fixtures/response_fixture.dart index cd604e4f..f894c77e 100644 --- a/chopper/test/fixtures/response_fixture.dart +++ b/chopper/test/fixtures/response_fixture.dart @@ -10,7 +10,7 @@ extension ResponseFixture on Response { } @internal -class ResponseFixtureFactory extends FixtureFactory> { +final class ResponseFixtureFactory extends FixtureFactory> { @override FixtureDefinition> definition() { final http.Response base = diff --git a/chopper/test/helpers/payload.dart b/chopper/test/helpers/payload.dart index 4c4ea8a3..6403f7e3 100644 --- a/chopper/test/helpers/payload.dart +++ b/chopper/test/helpers/payload.dart @@ -1,6 +1,6 @@ import 'package:equatable/equatable.dart'; -class Payload with EquatableMixin { +final class Payload with EquatableMixin { const Payload({ this.statusCode = 200, this.message = 'OK', diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 65c3ab03..a59baadb 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -1,24 +1,24 @@ name: chopper_built_value description: A built_value based Converter for Chopper. -version: 1.2.2 +version: 2.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: - built_value: ^8.0.0 - built_collection: ^5.0.0 - chopper: ^6.0.0 - http: ">=0.13.0 <2.0.0" + built_value: ^8.6.1 + built_collection: ^5.1.1 + chopper: ^7.0.0 + http: ^1.1.0 dev_dependencies: - test: ^1.16.4 - build_runner: ^2.0.0 - build_test: ^2.0.0 - built_value_generator: ^8.0.6 - lints: ^2.0.0 + test: ^1.24.4 + build_runner: ^2.4.6 + build_test: ^2.2.0 + built_value_generator: ^8.6.1 + lints: ^2.1.1 dependency_overrides: # Comment before publish diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 39353006..f91820f7 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -15,7 +15,8 @@ import 'package:logging/logging.dart'; import 'package:source_gen/source_gen.dart'; /// Code generator for [chopper.ChopperApi] annotated classes. -class ChopperGenerator extends GeneratorForAnnotation { +final class ChopperGenerator + extends GeneratorForAnnotation { const ChopperGenerator(); static final Logger _logger = Logger('Chopper Generator'); @@ -376,13 +377,11 @@ 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 static String _factoryForFunction(FunctionTypedElement function) => // ignore: deprecated_member_use - function.enclosingElement3 is ClassElement + function.enclosingElement is ClassElement // ignore: deprecated_member_use - ? '${function.enclosingElement3!.name}.${function.name}' + ? '${function.enclosingElement!.name}.${function.name}' : function.name!; static Map _getAnnotation( diff --git a/chopper_generator/lib/src/utils.dart b/chopper_generator/lib/src/utils.dart index f1be6482..315238ed 100644 --- a/chopper_generator/lib/src/utils.dart +++ b/chopper_generator/lib/src/utils.dart @@ -3,7 +3,7 @@ import 'package:chopper_generator/src/extensions.dart'; import 'package:code_builder/code_builder.dart'; import 'package:source_gen/source_gen.dart'; -class Utils { +final class Utils { static bool getMethodOptionalBody(ConstantReader method) => method.read('optionalBody').boolValue; diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 645318cd..e342ecc7 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -1,29 +1,29 @@ name: chopper_generator description: Chopper is an http client generator using source_gen, inspired by Retrofit -version: 6.0.3 +version: 7.0.0 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: - analyzer: '>=4.4.0 <6.0.0' - build: ^2.0.0 - built_collection: ^5.0.0 - chopper: ^6.0.0 - code_builder: ^4.3.0 - dart_style: ^2.0.0 - logging: ^1.0.0 - meta: ^1.3.0 - source_gen: ^1.0.0 + analyzer: ^5.13.0 + build: ^2.4.1 + built_collection: ^5.1.1 + chopper: ^7.0.0 + code_builder: ^4.5.0 + dart_style: ^2.3.2 + logging: ^1.2.0 + meta: ^1.9.1 + source_gen: ^1.4.0 dev_dependencies: - build_runner: ^2.0.0 + build_runner: ^2.4.6 build_verify: ^3.1.0 - http: ">=0.13.0 <2.0.0" - lints: ^2.0.0 - test: ^1.16.4 + http: ^1.1.0 + lints: ^2.1.1 + test: ^1.24.4 dependency_overrides: # Comment before publish diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 18b93a17..735d046c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ documentation: https://hadrien-lejard.gitbook.io/chopper/ #author: Hadrien Lejard environment: - sdk: '>=2.17.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: chopper: From 93b4e8ae5bd57bac3c58361d30f0679e1d558da5 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 30 Jul 2023 09:37:28 +0100 Subject: [PATCH 076/102] :twisted_rightwards_arrows: post release branch sync (#455) --- .github/workflows/publish.yml | 2 ++ .github/workflows/publish_dry_run.yml | 2 ++ chopper/CHANGELOG.md | 5 +++++ chopper_built_value/CHANGELOG.md | 4 ++++ chopper_generator/CHANGELOG.md | 6 ++++++ 5 files changed, 19 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 664fde42..41280a53 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,6 +43,8 @@ jobs: strategy: matrix: package: [ chopper, chopper_generator, chopper_built_value ] + fail-fast: true + max-parallel: 1 steps: - uses: dart-lang/setup-dart@v1 with: diff --git a/.github/workflows/publish_dry_run.yml b/.github/workflows/publish_dry_run.yml index b16ef582..14d51179 100644 --- a/.github/workflows/publish_dry_run.yml +++ b/.github/workflows/publish_dry_run.yml @@ -42,6 +42,8 @@ jobs: strategy: matrix: package: [ chopper, chopper_generator, chopper_built_value ] + fail-fast: true + max-parallel: 1 steps: - uses: dart-lang/setup-dart@v1 with: diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index d8a56152..b2846189 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 7.0.0 + +- Require Dart 3.0 or later +- Add base, final, and interface modifiers to some classes ([#453](https://github.com/lejard-h/chopper/pull/453)) + ## 6.1.4 - Fix Multipart for List and List ([#439](https://github.com/lejard-h/chopper/pull/439)) diff --git a/chopper_built_value/CHANGELOG.md b/chopper_built_value/CHANGELOG.md index 5fc1f1f7..7e536f4d 100644 --- a/chopper_built_value/CHANGELOG.md +++ b/chopper_built_value/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0 + +- Require Dart 3.0 or later + ## 1.2.2 - Update http constraint to ">=0.13.0 <2.0.0" ([#431](https://github.com/lejard-h/chopper/pull/431)) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 36c3e7ca..d7b19300 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 7.0.0 + +- Require Dart 3.0 or later +- Add final modifier to some classes ([#453](https://github.com/lejard-h/chopper/pull/453)) +- Replace deprecated Element.enclosingElement3 with Element.enclosingElement + ## 6.0.3 - Simplify library export From 7cd824cd3c37ee70cca1dfdbf1d0b1fe16c855c7 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 30 Jul 2023 11:55:41 +0100 Subject: [PATCH 077/102] :passport_control: fix CODEOWNERS (#456) Fixes: * Unknown owner on line 5: make sure @Guldem exists and has write access to the repository * Unknown owner on line 5: make sure @pixeltoast exists and has write access to the repository --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1b115426..3912df55 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,4 +2,4 @@ # 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 +* @JEuler @lejard-h @meysam1717 @stewemetal @techouse \ No newline at end of file From aa632837a84dfb27afb873b55332b81c488733ad Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 2 Aug 2023 08:13:26 +0200 Subject: [PATCH 078/102] :recycle: refactor chopper (#460) * refactor getService * refactor CurlInterceptor * refactor utils._Instance --- chopper/lib/src/base.dart | 9 +++------ chopper/lib/src/interceptor.dart | 31 +++++++++++++------------------ chopper/lib/src/utils.dart | 4 +--- chopper/test/base_test.dart | 14 +++++++++++++- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index 27ab57fc..b958d140 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -10,8 +10,6 @@ import 'package:chopper/src/utils.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; -Type _typeOf() => T; - @visibleForTesting const List allowedInterceptorsType = [ RequestInterceptor, @@ -168,15 +166,14 @@ base class ChopperClient { /// final todoService = chopper.getService(); /// ``` ServiceType getService() { - final Type serviceType = _typeOf(); - if (serviceType == dynamic || serviceType == ChopperService) { + if (ServiceType == dynamic || ServiceType == ChopperService) { throw Exception( 'Service type should be provided, `dynamic` is not allowed.', ); } - final ChopperService? service = _services[serviceType]; + final ChopperService? service = _services[ServiceType]; if (service == null) { - throw Exception('Service of type \'$serviceType\' not found.'); + throw Exception("Service of type '$ServiceType' not found."); } return service as ServiceType; diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 185bfc97..1431fc23 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -137,32 +137,27 @@ class CurlInterceptor implements RequestInterceptor { @override Future onRequest(Request request) async { 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\''; - }); + final List curlParts = ['curl -v -X ${baseRequest.method}']; + for (final MapEntry header in baseRequest.headers.entries) { + curlParts.add("-H '${header.key}: ${header.value}'"); + } // this is fairly naive, but it should cover most cases if (baseRequest is http.Request) { - final body = baseRequest.body; + final String body = baseRequest.body; if (body.isNotEmpty) { - curl += ' -d \'$body\''; + curlParts.add("-d '$body'"); } } if (baseRequest is http.MultipartRequest) { - final fields = baseRequest.fields; - final files = baseRequest.files; - fields.forEach((k, v) { - curl += ' -f \'$k: $v\''; - }); - for (var file in files) { - curl += ' -f \'${file.field}: ${file.filename ?? ''}\''; + for (final MapEntry field in baseRequest.fields.entries) { + curlParts.add("-f '${field.key}: ${field.value}'"); + } + for (final http.MultipartFile file in baseRequest.files) { + curlParts.add("-f '${file.field}: ${file.filename ?? ''}'"); } } - curl += ' "$url"'; - chopperLogger.info(curl); + curlParts.add('"${baseRequest.url}"'); + chopperLogger.info(curlParts.join(' ')); return request; } diff --git a/chopper/lib/src/utils.dart b/chopper/lib/src/utils.dart index ce78291c..ea4995ff 100644 --- a/chopper/lib/src/utils.dart +++ b/chopper/lib/src/utils.dart @@ -156,6 +156,4 @@ final class _Pair with EquatableMixin { bool isTypeOf() => _Instance() is _Instance; -class _Instance { - // -} +final class _Instance {} diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index d6486c2e..48cbb771 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -28,6 +28,18 @@ void main() { ); group('Base', () { + test('getService', () async { + final httpClient = MockClient( + (_) async => http.Response('get response', 200), + ); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + expect(service, isNotNull); + expect(service, isA()); + }); + test('get service errors', () async { final chopper = ChopperClient( baseUrl: baseUrl, @@ -38,7 +50,7 @@ void main() { } on Exception catch (e) { expect( e.toString(), - equals('Exception: Service of type \'HttpTestService\' not found.'), + equals("Exception: Service of type 'HttpTestService' not found."), ); } From a386e86a38dd17af5c9ffc4900cf67ae9748a4bd Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 2 Aug 2023 18:38:56 +0200 Subject: [PATCH 079/102] :recycle: refactor ChopperClient constructor (#461) --- chopper/lib/src/base.dart | 62 ++++++++++++++++++++----------------- chopper/test/base_test.dart | 40 +++++++++++++++++++++--- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index b958d140..e791c13b 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -45,11 +45,13 @@ base class ChopperClient { /// (statusCode < 200 || statusCode >= 300\). final ErrorConverter? errorConverter; - final Map _services = {}; - final _requestInterceptors = []; - final _responseInterceptors = []; - final _requestController = StreamController.broadcast(); - final _responseController = StreamController.broadcast(); + late final Map _services; + late final List _requestInterceptors; + late final List _responseInterceptors; + final StreamController _requestController = + StreamController.broadcast(); + final StreamController _responseController = + StreamController.broadcast(); final bool _clientIsInternal; @@ -112,44 +114,46 @@ base class ChopperClient { ChopperClient({ Uri? baseUrl, http.Client? client, - Iterable interceptors = const [], + Iterable? interceptors, this.authenticator, this.converter, this.errorConverter, - Iterable services = const [], + Iterable? services, }) : assert( - baseUrl == null || !baseUrl.hasQuery, - 'baseUrl should not contain query parameters.' - 'Use a request interceptor to add default query parameters'), + 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( - 'Unsupported type for interceptors, it only support the following types:\n' - '${allowedInterceptorsType.join('\n - ')}', - ); - } - - _requestInterceptors.addAll(interceptors.where(_isRequestInterceptor)); - _responseInterceptors.addAll(interceptors.where(_isResponseInterceptor)); - - services.toSet().forEach((s) { - s.client = this; - _services[s.definitionType] = s; - }); + _clientIsInternal = client == null, + assert( + interceptors?.every(_isAnInterceptor) ?? true, + 'Unsupported type for interceptors, it only support the following types:\n' + ' - ${allowedInterceptorsType.join('\n - ')}', + ), + _requestInterceptors = [ + ...?interceptors?.where(_isRequestInterceptor), + ], + _responseInterceptors = [ + ...?interceptors?.where(_isResponseInterceptor), + ] { + _services = { + for (final ChopperService service in services?.toSet() ?? []) + service.definitionType: service..client = this + }; } - bool _isRequestInterceptor(value) => + static bool _isRequestInterceptor(value) => value is RequestInterceptor || value is RequestInterceptorFunc; - bool _isResponseInterceptor(value) => + static bool _isResponseInterceptor(value) => value is ResponseInterceptor || value is ResponseInterceptorFunc1 || value is ResponseInterceptorFunc2 || value is DynamicResponseInterceptorFunc; - bool _isAnInterceptor(value) => + static bool _isAnInterceptor(value) => _isResponseInterceptor(value) || _isRequestInterceptor(value); /// Retrieve any service included in the [ChopperClient] @@ -182,7 +186,7 @@ base class ChopperClient { Future _encodeRequest(Request request) async => converter?.convertRequest(request) ?? request; - Future> _decodeResponse( + static Future> _decodeResponse( Response response, Converter withConverter, ) async => diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 48cbb771..3e6a6f01 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -647,20 +647,50 @@ void main() { }); test('wrong type for interceptor', () { + expect( + () => ChopperClient(interceptors: [(bool foo) => 'bar']), + throwsA(isA()), + ); + try { ChopperClient( interceptors: [ (bool foo) => 'bar', ], ); - } on ArgumentError catch (e) { + } on AssertionError catch (error) { expect( - e.toString(), - 'Invalid argument(s): Unsupported type for interceptors, it only support the following types:\n' - '${allowedInterceptorsType.join('\n - ')}', + error.toString(), + contains( + 'Unsupported type for interceptors, it only support the following types:\n' + ' - ${allowedInterceptorsType.join('\n - ')}', + ), ); } - }); + }, testOn: 'vm'); + + test('wrong type for interceptor', () { + expect( + () => ChopperClient(interceptors: [(bool foo) => 'bar']), + throwsA(isA()), + ); + + try { + ChopperClient( + interceptors: [ + (bool foo) => 'bar', + ], + ); + } on AssertionError catch (error) { + expect( + error.toString(), + contains( + 'Unsupported type for interceptors, it only support the following types:\\n' + ' - ${allowedInterceptorsType.join('\\n - ')}', + ), + ); + } + }, testOn: 'browser'); test('Query Map 1', () async { final httpClient = MockClient((request) async { From 7a6b5b9bebfeb45b647c93121e621d58da94cb49 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 2 Aug 2023 19:17:22 +0200 Subject: [PATCH 080/102] :arrow_up: update mono_repo (#462) --- .github/workflows/dart.yml | 40 ++++++------------------------- chopper_built_value/mono_pkg.yaml | 2 +- chopper_generator/mono_pkg.yaml | 2 +- mono_repo.yaml | 2 +- tool/ci.sh | 2 +- 5 files changed, 11 insertions(+), 37 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index ee10d7f9..6a6322d9 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,4 +1,4 @@ -# Created with package:mono_repo v6.5.5 +# Created with package:mono_repo v6.5.7 name: Dart CI on: push: @@ -37,20 +37,20 @@ jobs: name: Checkout repository uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - name: mono_repo self validate - run: dart pub global activate mono_repo 6.5.5 + run: dart pub global activate mono_repo 6.5.7 - 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 .`" + name: "analyze_and_format; PKGS: chopper, 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@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper;commands:format-analyze" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value-chopper_generator;commands:format-analyze" restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:chopper-chopper_built_value-chopper_generator os:ubuntu-latest;pub-cache-hosted;sdk:stable os:ubuntu-latest;pub-cache-hosted os:ubuntu-latest @@ -74,29 +74,6 @@ jobs: 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@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 - with: - path: "~/.pub-cache/hosted" - 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;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 - - name: Setup Dart SDK - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f - with: - sdk: stable - - id: checkout - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab - id: chopper_built_value_pub_upgrade name: chopper_built_value; dart pub upgrade run: dart pub upgrade @@ -125,8 +102,7 @@ jobs: working-directory: chopper_generator needs: - job_001 - - job_002 - job_004: + job_003: name: "unit_test; PKGS: chopper, chopper_built_value, chopper_generator; `dart pub global run coverage:test_with_coverage`" runs-on: ubuntu-latest steps: @@ -197,8 +173,7 @@ jobs: needs: - job_001 - job_002 - - job_003 - job_005: + job_004: name: "unit_test; PKGS: chopper, chopper_built_value; `dart test -p chrome`" runs-on: ubuntu-latest steps: @@ -240,4 +215,3 @@ jobs: needs: - job_001 - job_002 - - job_003 diff --git a/chopper_built_value/mono_pkg.yaml b/chopper_built_value/mono_pkg.yaml index ae7b5f25..8cce50b9 100644 --- a/chopper_built_value/mono_pkg.yaml +++ b/chopper_built_value/mono_pkg.yaml @@ -2,7 +2,7 @@ sdk: - stable stages: -- analyzer_and_format: +- analyze_and_format: - group: - format - analyze: --fatal-infos . diff --git a/chopper_generator/mono_pkg.yaml b/chopper_generator/mono_pkg.yaml index c0087871..df365840 100644 --- a/chopper_generator/mono_pkg.yaml +++ b/chopper_generator/mono_pkg.yaml @@ -2,7 +2,7 @@ sdk: - stable stages: -- analyzer_and_format: +- analyze_and_format: - group: - format - analyze: --fatal-infos . diff --git a/mono_repo.yaml b/mono_repo.yaml index 28e29828..1cdb37ac 100644 --- a/mono_repo.yaml +++ b/mono_repo.yaml @@ -12,7 +12,7 @@ github: - develop merge_stages: - - analyzer_and_format + - analyze_and_format - unit_test coverage_service: diff --git a/tool/ci.sh b/tool/ci.sh index 7449806d..9c1ac4c1 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Created with package:mono_repo v6.5.5 +# Created with package:mono_repo v6.5.7 # Support built in commands on windows out of the box. # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter") From e9fc26da51922ff89ddfb023150265ca59ca324a Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Thu, 3 Aug 2023 07:50:53 +0200 Subject: [PATCH 081/102] :sparkles: add final class modifier to generated Chopper API implementation (#463) --- chopper/example/definition.chopper.dart | 2 +- chopper/test/test_service.chopper.dart | 2 +- chopper_generator/lib/src/generator.dart | 1 + chopper_generator/test/test_service.chopper.dart | 2 +- example/lib/built_value_resource.chopper.dart | 2 +- example/lib/json_serializable.chopper.dart | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index c7c86ef7..a7fdf51a 100644 --- a/chopper/example/definition.chopper.dart +++ b/chopper/example/definition.chopper.dart @@ -7,7 +7,7 @@ part of 'definition.dart'; // ************************************************************************** // 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 { +final class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; this.client = client; diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index 45e1d737..d0379988 100644 --- a/chopper/test/test_service.chopper.dart +++ b/chopper/test/test_service.chopper.dart @@ -7,7 +7,7 @@ part of 'test_service.dart'; // ************************************************************************** // 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 { +final class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; this.client = client; diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index f91820f7..70ee0790 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -68,6 +68,7 @@ final class ChopperGenerator final Class classBuilder = Class((builder) { builder + ..modifier = ClassModifier.final$ ..name = name ..extend = refer(friendlyName) ..fields.add(_buildDefinitionTypeMethod(friendlyName)) diff --git a/chopper_generator/test/test_service.chopper.dart b/chopper_generator/test/test_service.chopper.dart index 45e1d737..d0379988 100644 --- a/chopper_generator/test/test_service.chopper.dart +++ b/chopper_generator/test/test_service.chopper.dart @@ -7,7 +7,7 @@ part of 'test_service.dart'; // ************************************************************************** // 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 { +final class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; this.client = client; diff --git a/example/lib/built_value_resource.chopper.dart b/example/lib/built_value_resource.chopper.dart index e30a468b..af0b742d 100644 --- a/example/lib/built_value_resource.chopper.dart +++ b/example/lib/built_value_resource.chopper.dart @@ -7,7 +7,7 @@ part of 'built_value_resource.dart'; // ************************************************************************** // 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 { +final class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; this.client = client; diff --git a/example/lib/json_serializable.chopper.dart b/example/lib/json_serializable.chopper.dart index 7a60b5ab..1aa9352e 100644 --- a/example/lib/json_serializable.chopper.dart +++ b/example/lib/json_serializable.chopper.dart @@ -7,7 +7,7 @@ part of 'json_serializable.dart'; // ************************************************************************** // 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 { +final class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; this.client = client; From e07afcfa14efe5debecf83930a2698c13a69b425 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Thu, 3 Aug 2023 18:09:14 +0200 Subject: [PATCH 082/102] :twisted_rightwards_arrows: post release branch sync (#466) --- chopper/CHANGELOG.md | 6 ++++++ chopper/pubspec.yaml | 2 +- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index b2846189..b226ff15 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 7.0.1 + +- Refactor ChopperClient constructor +- Refactor ChopperClient.getService +- Refactor CurlInterceptor + ## 7.0.0 - Require Dart 3.0 or later diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 5a6d5859..0e86235c 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: 7.0.0 +version: 7.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 d7b19300..7943acf4 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.1 + +- Add final class modifier to generated Chopper API implementations + ## 7.0.0 - Require Dart 3.0 or later diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index e342ecc7..ac8b3aa7 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: 7.0.0 +version: 7.0.1 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 2f7f4147ea75875082da3ffbaf9c2c52677d3c60 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 4 Aug 2023 10:43:08 +0200 Subject: [PATCH 083/102] :construction_worker: add Dependabot workflow (#467) --- .github/dependabot.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..68152415 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "pub" + directory: "/chopper" + schedule: + interval: "weekly" + target-branch: "develop" + - package-ecosystem: "pub" + directory: "/chopper_built_value" + schedule: + interval: "weekly" + target-branch: "develop" + - package-ecosystem: "pub" + directory: "/chopper_generator" + schedule: + interval: "weekly" + target-branch: "develop" \ No newline at end of file From 03f7bf73a835f453031a8c6795f81c761fe776d7 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 4 Aug 2023 10:58:51 +0200 Subject: [PATCH 084/102] :pencil2: fix typo in mono_repo.yaml (#468) --- .github/workflows/dart.yml | 2 -- mono_repo.yaml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 6a6322d9..0a20e47e 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -100,8 +100,6 @@ jobs: run: dart analyze --fatal-infos . if: "always() && steps.chopper_generator_pub_upgrade.conclusion == 'success'" working-directory: chopper_generator - needs: - - job_001 job_003: name: "unit_test; PKGS: chopper, chopper_built_value, chopper_generator; `dart pub global run coverage:test_with_coverage`" runs-on: ubuntu-latest diff --git a/mono_repo.yaml b/mono_repo.yaml index 1cdb37ac..5f0226ff 100644 --- a/mono_repo.yaml +++ b/mono_repo.yaml @@ -1,4 +1,4 @@ -self_validate: analyzer_and_format +self_validate: analyze_and_format github: on: From 8dbde76f8e4e3555266408dc260125cafd743f83 Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Sat, 5 Aug 2023 07:54:48 +0200 Subject: [PATCH 085/102] :sparkles: HttpLoggingInterceptor: allow to pass custom logger (#470) --- chopper/lib/src/http_logging_interceptor.dart | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/chopper/lib/src/http_logging_interceptor.dart b/chopper/lib/src/http_logging_interceptor.dart index 0be43965..f8965bee 100644 --- a/chopper/lib/src/http_logging_interceptor.dart +++ b/chopper/lib/src/http_logging_interceptor.dart @@ -5,6 +5,7 @@ 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:logging/logging.dart'; import 'package:meta/meta.dart'; enum Level { @@ -71,11 +72,13 @@ enum Level { @immutable class HttpLoggingInterceptor implements RequestInterceptor, ResponseInterceptor { - const HttpLoggingInterceptor({this.level = Level.body}) - : _logBody = level == Level.body, + HttpLoggingInterceptor({this.level = Level.body, Logger? logger}) + : _logger = logger ?? chopperLogger, + _logBody = level == Level.body, _logHeaders = level == Level.body || level == Level.headers; final Level level; + final Logger _logger; final bool _logBody; final bool _logHeaders; @@ -97,25 +100,25 @@ class HttpLoggingInterceptor } // Always start on a new line - chopperLogger.info(''); - chopperLogger.info(startRequestMessage); + _logger.info(''); + _logger.info(startRequestMessage); if (_logHeaders) { - base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); + base.headers.forEach((k, v) => _logger.info('$k: $v')); if (base.contentLength != null && base.headers['content-length'] == null) { - chopperLogger.info('content-length: ${base.contentLength}'); + _logger.info('content-length: ${base.contentLength}'); } } if (_logBody && bodyMessage.isNotEmpty) { - chopperLogger.info(''); - chopperLogger.info(bodyMessage); + _logger.info(''); + _logger.info(bodyMessage); } if (_logHeaders || _logBody) { - chopperLogger.info('--> END ${base.method}'); + _logger.info('--> END ${base.method}'); } return request; @@ -145,27 +148,27 @@ class HttpLoggingInterceptor } // Always start on a new line - chopperLogger.info(''); - chopperLogger.info( + _logger.info(''); + _logger.info( '<-- $reasonPhrase ${base.request?.method} ${base.request?.url.toString()}$bytes', ); if (_logHeaders) { - base.headers.forEach((k, v) => chopperLogger.info('$k: $v')); + base.headers.forEach((k, v) => _logger.info('$k: $v')); if (base.contentLength != null && base.headers['content-length'] == null) { - chopperLogger.info('content-length: ${base.contentLength}'); + _logger.info('content-length: ${base.contentLength}'); } } if (_logBody && bodyMessage.isNotEmpty) { - chopperLogger.info(''); - chopperLogger.info(bodyMessage); + _logger.info(''); + _logger.info(bodyMessage); } if (_logBody || _logHeaders) { - chopperLogger.info('<-- END HTTP'); + _logger.info('<-- END HTTP'); } return response; From 2e00b6a4763d0ded18049f1d9e23d1d88809fed8 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 7 Aug 2023 07:24:50 +0200 Subject: [PATCH 086/102] :construction_worker: use yq to manipulate pubspec.yaml in publish CI (#471) --- .github/workflows/publish.yml | 11 ++++++++--- .github/workflows/publish_dry_run.yml | 9 +++++++-- chopper/pubspec.yaml | 3 +++ chopper_built_value/pubspec.yaml | 1 - chopper_generator/pubspec.yaml | 1 - tool/publish.sh | 19 ------------------- 6 files changed, 18 insertions(+), 26 deletions(-) delete mode 100644 tool/publish.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 41280a53..a9da77ad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,11 +74,16 @@ jobs: run: | set -e mkdir -p $XDG_CONFIG_HOME/dart - echo '${{ secrets.CREDENTIAL_JSON }}' > "$XDG_CONFIG_HOME/dart/pub-credentials.json" + echo -n '${{ secrets.CREDENTIAL_JSON }}' > $XDG_CONFIG_HOME/dart/pub-credentials.json - name: Publish id: publish if: ${{ env.IS_VERSION_GREATER == 1 }} - run: bash tool/publish.sh ${{ matrix.package }} + run: | + set -e + pushd ${{ matrix.package }} || exit + yq -i 'del(.dependency_overrides)' pubspec.yaml + dart pub publish --force + popd || exit - name: Skip publish id: skip_publish if: ${{ env.IS_VERSION_GREATER == 0 }} @@ -87,4 +92,4 @@ jobs: id: cleanup if: ${{ always() }} run: | - rm -rf "$XDG_CONFIG_HOME/dart/pub-credentials.json" \ No newline at end of file + rm -rf $XDG_CONFIG_HOME/dart/pub-credentials.json diff --git a/.github/workflows/publish_dry_run.yml b/.github/workflows/publish_dry_run.yml index 14d51179..6ab66c84 100644 --- a/.github/workflows/publish_dry_run.yml +++ b/.github/workflows/publish_dry_run.yml @@ -70,8 +70,13 @@ jobs: - name: Publish (dry run) id: publish_dry_run if: ${{ env.IS_VERSION_GREATER == 1 }} - run: bash tool/publish.sh ${{ matrix.package }} --dry-run + run: | + set -e + pushd ${{ matrix.package }} || exit + yq -i 'del(.dependency_overrides)' pubspec.yaml + dart pub publish --dry-run + popd || exit - 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 + run: echo "Skipping publish (dry run) for ${{ matrix.package }} because the version is not greater than the one on pub.dev" diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 0e86235c..5f57ccb0 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -25,5 +25,8 @@ dev_dependencies: lints: ^2.1.1 test: ^1.24.4 transparent_image: ^2.0.1 + chopper_generator: ^7.0.0 + +dependency_overrides: chopper_generator: path: ../chopper_generator diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index a59baadb..b23d0a66 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -21,6 +21,5 @@ dev_dependencies: lints: ^2.1.1 dependency_overrides: - # Comment before publish chopper: path: ../chopper diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index ac8b3aa7..4d541b87 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -26,6 +26,5 @@ dev_dependencies: test: ^1.24.4 dependency_overrides: - # Comment before publish chopper: path: ../chopper diff --git a/tool/publish.sh b/tool/publish.sh deleted file mode 100644 index f5b81db2..00000000 --- a/tool/publish.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -e - -PKG=$1 -echo -e "\033[1mPKG: ${PKG}\033[22m" -pushd "${PKG}" - -sed '/Comment before publish$/,+2 d' pubspec.yaml > pubspec.temp.yaml -rm pubspec.yaml -mv pubspec.temp.yaml pubspec.yaml - -if [ "$2" == "--dry-run" ]; then - dart pub publish --dry-run -else - dart pub publish --force -fi - -popd \ No newline at end of file From 0af17f871fefefe9361311800eefe5a2e0137b80 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 7 Aug 2023 18:05:48 +0200 Subject: [PATCH 087/102] :twisted_rightwards_arrows: post release branch sync (#473) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index b226ff15..d45a4dee 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.2 + +- Add option to pass custom Logger to HttpLoggingInterceptor ([#470](https://github.com/lejard-h/chopper/pull/470)) + ## 7.0.1 - Refactor ChopperClient constructor diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 5f57ccb0..f6be31f8 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: 7.0.1 +version: 7.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 1a01b6096f355da01b93e6117fe5afa38230dee7 Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Thu, 10 Aug 2023 09:12:30 +0200 Subject: [PATCH 088/102] :sparkles: Attach request/response to interceptor log lines (#475) --- chopper/lib/src/chopper_log_record.dart | 13 ++++++ chopper/lib/src/http_logging_interceptor.dart | 45 ++++++++++++------- 2 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 chopper/lib/src/chopper_log_record.dart diff --git a/chopper/lib/src/chopper_log_record.dart b/chopper/lib/src/chopper_log_record.dart new file mode 100644 index 00000000..181ca492 --- /dev/null +++ b/chopper/lib/src/chopper_log_record.dart @@ -0,0 +1,13 @@ +import 'package:chopper/src/request.dart'; +import 'package:chopper/src/response.dart'; + +class ChopperLogRecord { + const ChopperLogRecord(this.message, {this.request, this.response}); + + final String message; + final Request? request; + final Response? response; + + @override + String toString() => message; +} diff --git a/chopper/lib/src/http_logging_interceptor.dart b/chopper/lib/src/http_logging_interceptor.dart index f8965bee..e2453673 100644 --- a/chopper/lib/src/http_logging_interceptor.dart +++ b/chopper/lib/src/http_logging_interceptor.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:chopper/src/chopper_log_record.dart'; import 'package:chopper/src/interceptor.dart'; import 'package:chopper/src/request.dart'; import 'package:chopper/src/response.dart'; @@ -100,25 +101,33 @@ class HttpLoggingInterceptor } // Always start on a new line - _logger.info(''); - _logger.info(startRequestMessage); + _logger.info(ChopperLogRecord('', request: request)); + _logger.info(ChopperLogRecord(startRequestMessage, request: request)); if (_logHeaders) { - base.headers.forEach((k, v) => _logger.info('$k: $v')); + base.headers.forEach( + (k, v) => _logger.info(ChopperLogRecord('$k: $v', request: request)), + ); if (base.contentLength != null && base.headers['content-length'] == null) { - _logger.info('content-length: ${base.contentLength}'); + _logger.info(ChopperLogRecord( + 'content-length: ${base.contentLength}', + request: request, + )); } } if (_logBody && bodyMessage.isNotEmpty) { - _logger.info(''); - _logger.info(bodyMessage); + _logger.info(ChopperLogRecord('', request: request)); + _logger.info(ChopperLogRecord(bodyMessage, request: request)); } if (_logHeaders || _logBody) { - _logger.info('--> END ${base.method}'); + _logger.info(ChopperLogRecord( + '--> END ${base.method}', + request: request, + )); } return request; @@ -148,27 +157,33 @@ class HttpLoggingInterceptor } // Always start on a new line - _logger.info(''); - _logger.info( + _logger.info(ChopperLogRecord('', response: response)); + _logger.info(ChopperLogRecord( '<-- $reasonPhrase ${base.request?.method} ${base.request?.url.toString()}$bytes', - ); + response: response, + )); if (_logHeaders) { - base.headers.forEach((k, v) => _logger.info('$k: $v')); + base.headers.forEach( + (k, v) => _logger.info(ChopperLogRecord('$k: $v', response: response)), + ); if (base.contentLength != null && base.headers['content-length'] == null) { - _logger.info('content-length: ${base.contentLength}'); + _logger.info(ChopperLogRecord( + 'content-length: ${base.contentLength}', + response: response, + )); } } if (_logBody && bodyMessage.isNotEmpty) { - _logger.info(''); - _logger.info(bodyMessage); + _logger.info(ChopperLogRecord('', response: response)); + _logger.info(ChopperLogRecord(bodyMessage, response: response)); } if (_logBody || _logHeaders) { - _logger.info('<-- END HTTP'); + _logger.info(ChopperLogRecord('<-- END HTTP', response: response)); } return response; From 764d57d28eff6a284d5e07b91141a822c246f6a7 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 11 Aug 2023 07:45:04 +0200 Subject: [PATCH 089/102] :twisted_rightwards_arrows: post release branch sync (#477) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index d45a4dee..2035bf9d 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.3 + +- Use ChopperLogRecord in HttpLoggingInterceptor to log lines ([#475](https://github.com/lejard-h/chopper/pull/475)) + ## 7.0.2 - Add option to pass custom Logger to HttpLoggingInterceptor ([#470](https://github.com/lejard-h/chopper/pull/470)) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index f6be31f8..847b1b79 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: 7.0.2 +version: 7.0.3 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From f8905ad97e09756fceac8e78c55fd634943465ab Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Mon, 14 Aug 2023 07:36:44 +0200 Subject: [PATCH 090/102] :adhesive_bandage: export ChopperLogRecord to library surface (#480) --- chopper/lib/chopper.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/chopper/lib/chopper.dart b/chopper/lib/chopper.dart index 2290febd..3c84987b 100644 --- a/chopper/lib/chopper.dart +++ b/chopper/lib/chopper.dart @@ -10,6 +10,7 @@ export 'src/constants.dart'; export 'src/extensions.dart'; export 'src/interceptor.dart'; export 'src/http_logging_interceptor.dart'; +export 'src/chopper_log_record.dart'; export 'src/request.dart'; export 'src/response.dart'; export 'src/utils.dart' hide mapToQuery; From c658d3980bbf3dbbcabcd81f378559764e7ae623 Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Mon, 14 Aug 2023 07:37:10 +0200 Subject: [PATCH 091/102] :label: make ChopperLogRecord final (#481) --- chopper/lib/src/chopper_log_record.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chopper/lib/src/chopper_log_record.dart b/chopper/lib/src/chopper_log_record.dart index 181ca492..1f02a036 100644 --- a/chopper/lib/src/chopper_log_record.dart +++ b/chopper/lib/src/chopper_log_record.dart @@ -1,7 +1,7 @@ import 'package:chopper/src/request.dart'; import 'package:chopper/src/response.dart'; -class ChopperLogRecord { +final class ChopperLogRecord { const ChopperLogRecord(this.message, {this.request, this.response}); final String message; From a0d70ace8554e0f9c4a422e1028f68b7fc9453c4 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Mon, 14 Aug 2023 08:51:41 +0100 Subject: [PATCH 092/102] :twisted_rightwards_arrows: post release branch sync (#483) --- chopper/CHANGELOG.md | 5 +++++ chopper/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index 2035bf9d..ba98f843 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 7.0.4 + +- Export ChopperLogRecord to library surface ([#480](https://github.com/lejard-h/chopper/pull/480)) +- Make ChopperLogRecord final ([#481](https://github.com/lejard-h/chopper/pull/481)) + ## 7.0.3 - Use ChopperLogRecord in HttpLoggingInterceptor to log lines ([#475](https://github.com/lejard-h/chopper/pull/475)) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 847b1b79..2ec6ec11 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: 7.0.3 +version: 7.0.4 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 2ab9d87829bad8a5728b7eb3884d6a6ea563223f Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Wed, 16 Aug 2023 19:29:51 +0100 Subject: [PATCH 093/102] :arrow_up: analyzer: ">=5.13.0 <7.0.0" (#484) --- 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 4d541b87..3c612ef7 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -8,7 +8,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - analyzer: ^5.13.0 + analyzer: ">=5.13.0 <7.0.0" build: ^2.4.1 built_collection: ^5.1.1 chopper: ^7.0.0 From 08bab6c169a84f97e908fbd025182edfe78e3e68 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Thu, 17 Aug 2023 10:22:01 +0100 Subject: [PATCH 094/102] :twisted_rightwards_arrows: post release branch sync (#486) --- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 7943acf4..55018b6f 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.2 + +- Update analyzer dependency to >=5.13.0 <7.0.0 ([#484](https://github.com/lejard-h/chopper/pull/484)) + ## 7.0.1 - Add final class modifier to generated Chopper API implementations diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 3c612ef7..6e2d7d4c 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: 7.0.1 +version: 7.0.2 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From 4c52b09cf5359d27075b4bdc81b67ef4094aba1c Mon Sep 17 00:00:00 2001 From: Hunter Wilhelm <71348224+hunterwilhelm@users.noreply.github.com> Date: Sat, 19 Aug 2023 03:04:03 -0600 Subject: [PATCH 095/102] Update README.md (#488) --- chopper/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chopper/README.md b/chopper/README.md index b3453bef..d2419358 100644 --- a/chopper/README.md +++ b/chopper/README.md @@ -37,9 +37,9 @@ Latest versions: ## Documentation -* [Getting started](getting-started.md) -* [Converters](converters/converters.md) -* [Interceptors](interceptors.md) +* [Getting started](../getting-started.md) +* [Converters](../converters/converters.md) +* [Interceptors](../interceptors.md) ## Examples From c7b67a619901d785aba5efbd7745e6f171de937e Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Tue, 22 Aug 2023 09:27:52 +0100 Subject: [PATCH 096/102] :twisted_rightwards_arrows: post release branch sync (#491) --- chopper/CHANGELOG.md | 4 ++++ chopper/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index ba98f843..d820ae48 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.5 + +- Fix documentation links in README ([#488](https://github.com/lejard-h/chopper/pull/488)) + ## 7.0.4 - Export ChopperLogRecord to library surface ([#480](https://github.com/lejard-h/chopper/pull/480)) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 2ec6ec11..0ddea437 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: 7.0.4 +version: 7.0.5 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From e706150b9a57863114a43de337a9c30b7257a833 Mon Sep 17 00:00:00 2001 From: Martin Alejandro Escobar Espinel <56127727+martinale14@users.noreply.github.com> Date: Thu, 31 Aug 2023 03:08:36 -0500 Subject: [PATCH 097/102] Adding support for top level variables (#493) --- chopper/lib/src/annotations.dart | 2 + chopper/test/base_test.dart | 141 ++++ chopper/test/ensure_build_test.dart | 20 +- .../test/test_service_variable.chopper.dart | 641 ++++++++++++++++++ chopper/test/test_service_variable.dart | 220 ++++++ chopper_generator/lib/src/generator.dart | 75 +- chopper_generator/test/ensure_build_test.dart | 21 +- .../test/test_service_variable.chopper.dart | 641 ++++++++++++++++++ .../test/test_service_variable.dart | 220 ++++++ 9 files changed, 1956 insertions(+), 25 deletions(-) create mode 100644 chopper/test/test_service_variable.chopper.dart create mode 100644 chopper/test/test_service_variable.dart create mode 100644 chopper_generator/test/test_service_variable.chopper.dart create mode 100644 chopper_generator/test/test_service_variable.dart diff --git a/chopper/lib/src/annotations.dart b/chopper/lib/src/annotations.dart index 9030d432..4fa5e492 100644 --- a/chopper/lib/src/annotations.dart +++ b/chopper/lib/src/annotations.dart @@ -22,6 +22,8 @@ import 'package:meta/meta.dart'; @immutable final class ChopperApi { /// A part of a URL that every request defined inside a class annotated with [ChopperApi] will be prefixed with. + /// + /// The `baseUrl` can be a top level constant string variable. final String baseUrl; const ChopperApi({ diff --git a/chopper/test/base_test.dart b/chopper/test/base_test.dart index 3e6a6f01..dee04012 100644 --- a/chopper/test/base_test.dart +++ b/chopper/test/base_test.dart @@ -9,8 +9,10 @@ import 'package:http/testing.dart'; import 'package:test/test.dart'; import 'test_service.dart'; +import 'test_service_variable.dart'; final baseUrl = Uri.parse('http://localhost:8000'); +const String testEnv = 'https://localhost:4000'; void main() { ChopperClient buildClient([ @@ -22,6 +24,7 @@ void main() { services: [ // the generated service HttpTestService.create(), + HttpTestServiceVariable.create(), ], client: httpClient, errorConverter: errorConverter, @@ -35,9 +38,12 @@ void main() { final chopper = buildClient(httpClient); final service = chopper.getService(); + final serviceVariable = chopper.getService(); expect(service, isNotNull); + expect(serviceVariable, isNotNull); expect(service, isA()); + expect(serviceVariable, isA()); }); test('get service errors', () async { @@ -86,6 +92,28 @@ void main() { httpClient.close(); }); + test('GET Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/get/1234'), + ); + expect(request.method, equals('GET')); + + return http.Response('get response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.getTest('1234', dynamicHeader: ''); + + expect(response.body, equals('get response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('GET stream', () async { final httpClient = MockClient.streaming((request, stream) async { expect( @@ -231,6 +259,29 @@ void main() { httpClient.close(); }); + test('POST Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/post'), + ); + expect(request.method, equals('POST')); + expect(request.body, equals('post body')); + + return http.Response('post response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.postTest('post body'); + + expect(response.body, equals('post response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('POST with streamed body', () async { final httpClient = MockClient((request) async { expect( @@ -282,6 +333,29 @@ void main() { httpClient.close(); }); + test('PUT Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/put/1234'), + ); + expect(request.method, equals('PUT')); + expect(request.body, equals('put body')); + + return http.Response('put response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.putTest('1234', 'put body'); + + expect(response.body, equals('put response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('PATCH', () async { final httpClient = MockClient((request) async { expect( @@ -305,6 +379,29 @@ void main() { httpClient.close(); }); + test('PATCH Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/patch/1234'), + ); + expect(request.method, equals('PATCH')); + expect(request.body, equals('patch body')); + + return http.Response('patch response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.patchTest('1234', 'patch body'); + + expect(response.body, equals('patch response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('DELETE', () async { final httpClient = MockClient((request) async { expect( @@ -327,6 +424,28 @@ void main() { httpClient.close(); }); + test('DELETE Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/delete/1234'), + ); + expect(request.method, equals('DELETE')); + + return http.Response('delete response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.deleteTest('1234'); + + expect(response.body, equals('delete response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('Head', () async { final httpClient = MockClient((request) async { expect( @@ -349,6 +468,28 @@ void main() { httpClient.close(); }); + test('Head Variable', () async { + final httpClient = MockClient((request) async { + expect( + request.url.toString(), + equals('$testEnv/head'), + ); + expect(request.method, equals('HEAD')); + + return http.Response('head response', 200); + }); + + final chopper = buildClient(httpClient); + final service = chopper.getService(); + + final response = await service.headTest(); + + expect(response.body, equals('head response')); + expect(response.statusCode, equals(200)); + + httpClient.close(); + }); + test('const headers', () async { final client = MockClient((http.Request req) async { expect(req.headers.containsKey('foo'), isTrue); diff --git a/chopper/test/ensure_build_test.dart b/chopper/test/ensure_build_test.dart index 73f46337..a599c5d2 100644 --- a/chopper/test/ensure_build_test.dart +++ b/chopper/test/ensure_build_test.dart @@ -6,11 +6,19 @@ import 'package:test/test.dart'; void main() { test( 'ensure_build', - () => expectBuildClean( - packageRelativeDirectory: 'chopper', - gitDiffPathArguments: [ - 'test/test_service.chopper.dart', - ], - ), + () { + expectBuildClean( + packageRelativeDirectory: 'chopper', + gitDiffPathArguments: [ + 'test/test_service.chopper.dart', + ], + ); + expectBuildClean( + packageRelativeDirectory: 'chopper', + gitDiffPathArguments: [ + 'test/test_service_variable.chopper.dart', + ], + ); + }, ); } diff --git a/chopper/test/test_service_variable.chopper.dart b/chopper/test/test_service_variable.chopper.dart new file mode 100644 index 00000000..c4423e71 --- /dev/null +++ b/chopper/test/test_service_variable.chopper.dart @@ -0,0 +1,641 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_service_variable.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps +final class _$HttpTestServiceVariable extends HttpTestServiceVariable { + _$HttpTestServiceVariable([ChopperClient? client]) { + if (client == null) return; + this.client = client; + } + + @override + final definitionType = HttpTestServiceVariable; + + @override + Future> getTest( + String id, { + required String dynamicHeader, + }) { + final Uri $url = Uri.parse('${service}/get/${id}'); + final Map $headers = { + 'test': dynamicHeader, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> headTest() { + final Uri $url = Uri.parse('${service}/head'); + final Request $request = Request( + 'HEAD', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> optionsTest() { + final Uri $url = Uri.parse('${service}/options'); + final Request $request = Request( + 'OPTIONS', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>>> getStreamTest() { + final Uri $url = Uri.parse('${service}/get'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send>, int>($request); + } + + @override + Future> getAll() { + final Uri $url = Uri.parse('${service}'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getAllWithTrailingSlash() { + final Uri $url = Uri.parse('${service}/'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getQueryTest({ + String name = '', + int? number, + int? def = 42, + }) { + final Uri $url = Uri.parse('${service}/query'); + final Map $params = { + 'name': name, + 'int': number, + 'default_value': def, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest(Map query) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = query; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest2( + Map query, { + bool? test, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = {'test': test}; + $params.addAll(query); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest3({ + String name = '', + int? number, + Map filters = const {}, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest4({ + String name = '', + int? number, + Map? filters, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters ?? const {}); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest5({Map? filters}) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = filters ?? const {}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getBody(dynamic body) { + final Uri $url = Uri.parse('${service}/get_body'); + final $body = body; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postTest(String data) { + final Uri $url = Uri.parse('${service}/post'); + final $body = data; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postStreamTest(Stream> byteStream) { + final Uri $url = Uri.parse('${service}/post'); + final $body = byteStream; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> putTest( + String test, + String data, + ) { + final Uri $url = Uri.parse('${service}/put/${test}'); + final $body = data; + final Request $request = Request( + 'PUT', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> deleteTest(String id) { + final Uri $url = Uri.parse('${service}/delete/${id}'); + final Map $headers = { + 'foo': 'bar', + }; + final Request $request = Request( + 'DELETE', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> patchTest( + String id, + String data, + ) { + final Uri $url = Uri.parse('${service}/patch/${id}'); + final $body = data; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> mapTest(Map map) { + final Uri $url = Uri.parse('${service}/map'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postForm(Map fields) { + final Uri $url = Uri.parse('${service}/form/body'); + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> postFormUsingHeaders(Map fields) { + final Uri $url = Uri.parse('${service}/form/body'); + final Map $headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> postFormFields( + String foo, + int bar, + ) { + final Uri $url = Uri.parse('${service}/form/body/fields'); + final $body = { + 'foo': foo, + 'bar': bar, + }; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> forceJsonTest(Map map) { + final Uri $url = Uri.parse('${service}/map/json'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: customConvertRequest, + responseConverter: customConvertResponse, + ); + } + + @override + Future> postResources( + Map a, + Map b, + ) { + final Uri $url = Uri.parse('${service}/multi'); + final List $parts = [ + PartValue>( + '1', + a, + ), + PartValue>( + '2', + b, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postFile(List bytes) { + final Uri $url = Uri.parse('${service}/file'); + final List $parts = [ + PartValueFile>( + 'file', + bytes, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postImage(List imageData) { + final Uri $url = Uri.parse('${service}/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, { + String? id, + }) { + final Uri $url = Uri.parse('${service}/file'); + final List $parts = [ + PartValue( + 'id', + id, + ), + PartValueFile( + 'file', + file, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postListFiles(List files) { + final Uri $url = Uri.parse('${service}/files'); + final List $parts = [ + PartValueFile>( + 'files', + files, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postMultipartList({ + required List ints, + required List doubles, + required List nums, + required List strings, + }) { + final Uri $url = Uri.parse('${service}/multipart_list'); + final List $parts = [ + PartValue>( + 'ints', + ints, + ), + PartValue>( + 'doubles', + doubles, + ), + PartValue>( + 'nums', + nums, + ), + PartValue>( + 'strings', + strings, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future fullUrl() { + final Uri $url = Uri.parse('https://test.com'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>> listString() { + final Uri $url = Uri.parse('${service}/list/string'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send, String>($request); + } + + @override + Future> noBody() { + final Uri $url = Uri.parse('${service}/no-body'); + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getUsingQueryParamIncludeNullQueryVars({ + String? foo, + String? bar, + String? baz, + }) { + final Uri $url = + Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/map_query_param'); + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getUsingMapQueryParamIncludeNullQueryVars( + Map value) { + final Uri $url = + Uri.parse('${service}/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) { + final Uri $url = Uri.parse('${service}/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_variable.dart b/chopper/test/test_service_variable.dart new file mode 100644 index 00000000..81532976 --- /dev/null +++ b/chopper/test/test_service_variable.dart @@ -0,0 +1,220 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' show MultipartFile; + +part 'test_service_variable.chopper.dart'; + +const String service = 'https://localhost:4000'; + +@ChopperApi(baseUrl: service) +abstract class HttpTestServiceVariable extends ChopperService { + static HttpTestServiceVariable create([ChopperClient? client]) => + _$HttpTestServiceVariable(client); + + @Get(path: 'get/{id}') + Future> getTest( + @Path() String id, { + @Header('test') required String dynamicHeader, + }); + + @Head(path: 'head') + Future headTest(); + + @Options(path: 'options') + Future optionsTest(); + + @Get(path: 'get') + Future>>> getStreamTest(); + + @Get(path: '') + Future getAll(); + + @Get(path: '/') + Future getAllWithTrailingSlash(); + + @Get(path: 'query') + Future getQueryTest({ + @Query('name') String name = '', + @Query('int') int? number, + @Query('default_value') int? def = 42, + }); + + @Get(path: 'query_map') + Future getQueryMapTest(@QueryMap() Map query); + + @Get(path: 'query_map') + Future getQueryMapTest2( + @QueryMap() Map query, { + @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); + + @Post(path: 'post') + Future postTest(@Body() String data); + + @Post(path: 'post') + Future postStreamTest(@Body() Stream> byteStream); + + @Put(path: 'put/{id}') + Future putTest(@Path('id') String test, @Body() String data); + + @Delete(path: 'delete/{id}', headers: {'foo': 'bar'}) + Future deleteTest(@Path() String id); + + @Patch(path: 'patch/{id}') + Future patchTest(@Path() String id, @Body() String data); + + @Post(path: 'map') + Future mapTest(@Body() Map map); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body') + Future postForm(@Body() Map fields); + + @Post(path: 'form/body', headers: {contentTypeKey: formEncodedHeaders}) + Future postFormUsingHeaders(@Body() Map fields); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body/fields') + Future postFormFields(@Field() String foo, @Field() int bar); + + @Post(path: 'map/json') + @FactoryConverter( + request: customConvertRequest, + response: customConvertResponse, + ) + Future forceJsonTest(@Body() Map map); + + @Post(path: 'multi') + @multipart + Future postResources( + @Part('1') Map a, + @Part('2') Map b, + ); + + @Post(path: 'file') + @multipart + Future postFile( + @PartFile('file') List bytes, + ); + + @Post(path: 'image') + @multipart + Future postImage( + @PartFile('image') List imageData, + ); + + @Post(path: 'file') + @multipart + Future postMultipartFile( + @PartFile() MultipartFile file, { + @Part() String? id, + }); + + @Post(path: 'files') + @multipart + Future postListFiles(@PartFile() List files); + + @Post(path: 'multipart_list') + @multipart + Future postMultipartList({ + @Part('ints') required List ints, + @Part('doubles') required List doubles, + @Part('nums') required List nums, + @Part('strings') required List strings, + }); + + @Get(path: 'https://test.com') + Future fullUrl(); + + @Get(path: '/list/string') + Future>> listString(); + + @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, + ); + + @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_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, + ); +} + +Request customConvertRequest(Request req) { + final r = JsonConverter().convertRequest(req); + + return applyHeader(r, 'customConverter', 'true'); +} + +Response customConvertResponse(Response res) => + res.copyWith(body: json.decode(res.body)); + +Request convertForm(Request req) { + req = applyHeader(req, contentTypeKey, formEncodedHeaders); + + if (req.body is Map) { + final body = {}; + + req.body.forEach((key, val) { + if (val != null) { + body[key.toString()] = val.toString(); + } + }); + + req = req.copyWith(body: body); + } + + return req; +} diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 70ee0790..81518109 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -63,8 +63,21 @@ final class ChopperGenerator final String friendlyName = element.name; final String name = '_\$$friendlyName'; - final String baseUrl = - annotation.peek(Vars.baseUrl.toString())?.stringValue ?? ''; + + final ConstantReader? baseUrlReader = + annotation.peek(Vars.baseUrl.toString()); + + TopLevelVariableElement? baseUrlVariableElement; + + final VariableElement? posibleBaseUrl = baseUrlReader?.objectValue.variable; + + if (posibleBaseUrl is TopLevelVariableElement && + posibleBaseUrl.type.isDartCoreString && + posibleBaseUrl.isConst) { + baseUrlVariableElement = posibleBaseUrl; + } + + final String baseUrl = baseUrlReader?.stringValue ?? ''; final Class classBuilder = Class((builder) { builder @@ -73,7 +86,11 @@ final class ChopperGenerator ..extend = refer(friendlyName) ..fields.add(_buildDefinitionTypeMethod(friendlyName)) ..constructors.add(_generateConstructor()) - ..methods.addAll(_parseMethods(element, baseUrl)); + ..methods.addAll(_parseMethods( + element, + baseUrl, + baseUrlVariableElement, + )); }); const String ignore = '// ignore_for_file: ' @@ -102,7 +119,11 @@ final class ChopperGenerator }, ); - static Iterable _parseMethods(ClassElement element, String baseUrl) => + static Iterable _parseMethods( + ClassElement element, + String baseUrl, + TopLevelVariableElement? baseUrlVariableElement, + ) => element.methods .where( (MethodElement method) => @@ -110,9 +131,19 @@ final class ChopperGenerator method.isAbstract && method.returnType.isDartAsyncFuture, ) - .map((MethodElement m) => _generateMethod(m, baseUrl)); + .map( + (MethodElement m) => _generateMethod( + m, + baseUrl, + baseUrlVariableElement, + ), + ); - static Method _generateMethod(MethodElement m, String baseUrl) { + static Method _generateMethod( + MethodElement m, + String baseUrl, + TopLevelVariableElement? baseUrlVariableElement, + ) { final ConstantReader? method = _getMethodAnnotation(m); final bool multipart = _hasAnnotation(m, chopper.Multipart); final ConstantReader? factoryConverter = _getFactoryConverterAnnotation(m); @@ -138,7 +169,12 @@ final class ChopperGenerator _getAnnotation(m, chopper.PartFileMap); final Code? headers = _generateHeaders(m, method!); - final Expression url = _generateUrl(method, paths, baseUrl); + final Expression url = _generateUrl( + method, + paths, + baseUrl, + baseUrlVariableElement, + ); final DartType? responseType = _getResponseType(m.returnType); final DartType? responseInnerType = _getResponseInnerType(m.returnType) ?? responseType; @@ -481,6 +517,7 @@ final class ChopperGenerator ConstantReader method, Map paths, String baseUrl, + TopLevelVariableElement? baseUrlVariableElement, ) { String path = Utils.getMethodPath(method); paths.forEach((p, ConstantReader r) { @@ -498,14 +535,26 @@ final class ChopperGenerator return _generateUri(''); } - if (path.isNotEmpty && - baseUrl.isNotEmpty && - !baseUrl.endsWith('/') && - !path.startsWith('/')) { - return _generateUri('$baseUrl/$path'); + String finalBaseUrl = baseUrl; + + if (baseUrlVariableElement != null) { + finalBaseUrl = '\${${baseUrlVariableElement.displayName}}'; + } + + if (path.isNotEmpty && baseUrl.isNotEmpty) { + bool pathHasSlash = path.startsWith('/'); + bool baseUrlHasSlash = baseUrl.endsWith('/'); + + if ((!baseUrlHasSlash && !pathHasSlash)) { + return _generateUri('$finalBaseUrl/$path'); + } + + if (baseUrlHasSlash && pathHasSlash) { + return _generateUri('$finalBaseUrl${path.replaceFirst('/', '')}'); + } } - return _generateUri('$baseUrl$path'); + return _generateUri('$finalBaseUrl$path'.replaceAll('//', '/')); } static Expression _generateUri(String url) => diff --git a/chopper_generator/test/ensure_build_test.dart b/chopper_generator/test/ensure_build_test.dart index cb5dc60f..c708d8d1 100644 --- a/chopper_generator/test/ensure_build_test.dart +++ b/chopper_generator/test/ensure_build_test.dart @@ -6,11 +6,20 @@ import 'package:test/test.dart'; void main() { test( 'ensure_build', - () => expectBuildClean( - packageRelativeDirectory: 'chopper_generator', - gitDiffPathArguments: [ - 'test/test_service.chopper.dart', - ], - ), + () { + expectBuildClean( + packageRelativeDirectory: 'chopper_generator', + gitDiffPathArguments: [ + 'test/test_service.chopper.dart', + ], + ); + + expectBuildClean( + packageRelativeDirectory: 'chopper_generator', + gitDiffPathArguments: [ + 'test/test_service_variable.chopper.dart', + ], + ); + }, ); } diff --git a/chopper_generator/test/test_service_variable.chopper.dart b/chopper_generator/test/test_service_variable.chopper.dart new file mode 100644 index 00000000..c4423e71 --- /dev/null +++ b/chopper_generator/test/test_service_variable.chopper.dart @@ -0,0 +1,641 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_service_variable.dart'; + +// ************************************************************************** +// ChopperGenerator +// ************************************************************************** + +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps +final class _$HttpTestServiceVariable extends HttpTestServiceVariable { + _$HttpTestServiceVariable([ChopperClient? client]) { + if (client == null) return; + this.client = client; + } + + @override + final definitionType = HttpTestServiceVariable; + + @override + Future> getTest( + String id, { + required String dynamicHeader, + }) { + final Uri $url = Uri.parse('${service}/get/${id}'); + final Map $headers = { + 'test': dynamicHeader, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> headTest() { + final Uri $url = Uri.parse('${service}/head'); + final Request $request = Request( + 'HEAD', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> optionsTest() { + final Uri $url = Uri.parse('${service}/options'); + final Request $request = Request( + 'OPTIONS', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>>> getStreamTest() { + final Uri $url = Uri.parse('${service}/get'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send>, int>($request); + } + + @override + Future> getAll() { + final Uri $url = Uri.parse('${service}'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getAllWithTrailingSlash() { + final Uri $url = Uri.parse('${service}/'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getQueryTest({ + String name = '', + int? number, + int? def = 42, + }) { + final Uri $url = Uri.parse('${service}/query'); + final Map $params = { + 'name': name, + 'int': number, + 'default_value': def, + }; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest(Map query) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = query; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest2( + Map query, { + bool? test, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = {'test': test}; + $params.addAll(query); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest3({ + String name = '', + int? number, + Map filters = const {}, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest4({ + String name = '', + int? number, + Map? filters, + }) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = { + 'name': name, + 'number': number, + }; + $params.addAll(filters ?? const {}); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getQueryMapTest5({Map? filters}) { + final Uri $url = Uri.parse('${service}/query_map'); + final Map $params = filters ?? const {}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getBody(dynamic body) { + final Uri $url = Uri.parse('${service}/get_body'); + final $body = body; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postTest(String data) { + final Uri $url = Uri.parse('${service}/post'); + final $body = data; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postStreamTest(Stream> byteStream) { + final Uri $url = Uri.parse('${service}/post'); + final $body = byteStream; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> putTest( + String test, + String data, + ) { + final Uri $url = Uri.parse('${service}/put/${test}'); + final $body = data; + final Request $request = Request( + 'PUT', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> deleteTest(String id) { + final Uri $url = Uri.parse('${service}/delete/${id}'); + final Map $headers = { + 'foo': 'bar', + }; + final Request $request = Request( + 'DELETE', + $url, + client.baseUrl, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> patchTest( + String id, + String data, + ) { + final Uri $url = Uri.parse('${service}/patch/${id}'); + final $body = data; + final Request $request = Request( + 'PATCH', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> mapTest(Map map) { + final Uri $url = Uri.parse('${service}/map'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send($request); + } + + @override + Future> postForm(Map fields) { + final Uri $url = Uri.parse('${service}/form/body'); + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> postFormUsingHeaders(Map fields) { + final Uri $url = Uri.parse('${service}/form/body'); + final Map $headers = { + 'content-type': 'application/x-www-form-urlencoded', + }; + final $body = fields; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + headers: $headers, + ); + return client.send($request); + } + + @override + Future> postFormFields( + String foo, + int bar, + ) { + final Uri $url = Uri.parse('${service}/form/body/fields'); + final $body = { + 'foo': foo, + 'bar': bar, + }; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: convertForm, + ); + } + + @override + Future> forceJsonTest(Map map) { + final Uri $url = Uri.parse('${service}/map/json'); + final $body = map; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + body: $body, + ); + return client.send( + $request, + requestConverter: customConvertRequest, + responseConverter: customConvertResponse, + ); + } + + @override + Future> postResources( + Map a, + Map b, + ) { + final Uri $url = Uri.parse('${service}/multi'); + final List $parts = [ + PartValue>( + '1', + a, + ), + PartValue>( + '2', + b, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postFile(List bytes) { + final Uri $url = Uri.parse('${service}/file'); + final List $parts = [ + PartValueFile>( + 'file', + bytes, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postImage(List imageData) { + final Uri $url = Uri.parse('${service}/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, { + String? id, + }) { + final Uri $url = Uri.parse('${service}/file'); + final List $parts = [ + PartValue( + 'id', + id, + ), + PartValueFile( + 'file', + file, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postListFiles(List files) { + final Uri $url = Uri.parse('${service}/files'); + final List $parts = [ + PartValueFile>( + 'files', + files, + ) + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future> postMultipartList({ + required List ints, + required List doubles, + required List nums, + required List strings, + }) { + final Uri $url = Uri.parse('${service}/multipart_list'); + final List $parts = [ + PartValue>( + 'ints', + ints, + ), + PartValue>( + 'doubles', + doubles, + ), + PartValue>( + 'nums', + nums, + ), + PartValue>( + 'strings', + strings, + ), + ]; + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + parts: $parts, + multipart: true, + ); + return client.send($request); + } + + @override + Future fullUrl() { + final Uri $url = Uri.parse('https://test.com'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future>> listString() { + final Uri $url = Uri.parse('${service}/list/string'); + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + ); + return client.send, String>($request); + } + + @override + Future> noBody() { + final Uri $url = Uri.parse('${service}/no-body'); + final Request $request = Request( + 'POST', + $url, + client.baseUrl, + ); + return client.send($request); + } + + @override + Future> getUsingQueryParamIncludeNullQueryVars({ + String? foo, + String? bar, + String? baz, + }) { + final Uri $url = + Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/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 Uri $url = Uri.parse('${service}/map_query_param'); + final Map $params = {'value': value}; + final Request $request = Request( + 'GET', + $url, + client.baseUrl, + parameters: $params, + ); + return client.send($request); + } + + @override + Future> getUsingMapQueryParamIncludeNullQueryVars( + Map value) { + final Uri $url = + Uri.parse('${service}/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) { + final Uri $url = Uri.parse('${service}/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_generator/test/test_service_variable.dart b/chopper_generator/test/test_service_variable.dart new file mode 100644 index 00000000..81532976 --- /dev/null +++ b/chopper_generator/test/test_service_variable.dart @@ -0,0 +1,220 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:chopper/chopper.dart'; +import 'package:http/http.dart' show MultipartFile; + +part 'test_service_variable.chopper.dart'; + +const String service = 'https://localhost:4000'; + +@ChopperApi(baseUrl: service) +abstract class HttpTestServiceVariable extends ChopperService { + static HttpTestServiceVariable create([ChopperClient? client]) => + _$HttpTestServiceVariable(client); + + @Get(path: 'get/{id}') + Future> getTest( + @Path() String id, { + @Header('test') required String dynamicHeader, + }); + + @Head(path: 'head') + Future headTest(); + + @Options(path: 'options') + Future optionsTest(); + + @Get(path: 'get') + Future>>> getStreamTest(); + + @Get(path: '') + Future getAll(); + + @Get(path: '/') + Future getAllWithTrailingSlash(); + + @Get(path: 'query') + Future getQueryTest({ + @Query('name') String name = '', + @Query('int') int? number, + @Query('default_value') int? def = 42, + }); + + @Get(path: 'query_map') + Future getQueryMapTest(@QueryMap() Map query); + + @Get(path: 'query_map') + Future getQueryMapTest2( + @QueryMap() Map query, { + @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); + + @Post(path: 'post') + Future postTest(@Body() String data); + + @Post(path: 'post') + Future postStreamTest(@Body() Stream> byteStream); + + @Put(path: 'put/{id}') + Future putTest(@Path('id') String test, @Body() String data); + + @Delete(path: 'delete/{id}', headers: {'foo': 'bar'}) + Future deleteTest(@Path() String id); + + @Patch(path: 'patch/{id}') + Future patchTest(@Path() String id, @Body() String data); + + @Post(path: 'map') + Future mapTest(@Body() Map map); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body') + Future postForm(@Body() Map fields); + + @Post(path: 'form/body', headers: {contentTypeKey: formEncodedHeaders}) + Future postFormUsingHeaders(@Body() Map fields); + + @FactoryConverter(request: convertForm) + @Post(path: 'form/body/fields') + Future postFormFields(@Field() String foo, @Field() int bar); + + @Post(path: 'map/json') + @FactoryConverter( + request: customConvertRequest, + response: customConvertResponse, + ) + Future forceJsonTest(@Body() Map map); + + @Post(path: 'multi') + @multipart + Future postResources( + @Part('1') Map a, + @Part('2') Map b, + ); + + @Post(path: 'file') + @multipart + Future postFile( + @PartFile('file') List bytes, + ); + + @Post(path: 'image') + @multipart + Future postImage( + @PartFile('image') List imageData, + ); + + @Post(path: 'file') + @multipart + Future postMultipartFile( + @PartFile() MultipartFile file, { + @Part() String? id, + }); + + @Post(path: 'files') + @multipart + Future postListFiles(@PartFile() List files); + + @Post(path: 'multipart_list') + @multipart + Future postMultipartList({ + @Part('ints') required List ints, + @Part('doubles') required List doubles, + @Part('nums') required List nums, + @Part('strings') required List strings, + }); + + @Get(path: 'https://test.com') + Future fullUrl(); + + @Get(path: '/list/string') + Future>> listString(); + + @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, + ); + + @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_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, + ); +} + +Request customConvertRequest(Request req) { + final r = JsonConverter().convertRequest(req); + + return applyHeader(r, 'customConverter', 'true'); +} + +Response customConvertResponse(Response res) => + res.copyWith(body: json.decode(res.body)); + +Request convertForm(Request req) { + req = applyHeader(req, contentTypeKey, formEncodedHeaders); + + if (req.body is Map) { + final body = {}; + + req.body.forEach((key, val) { + if (val != null) { + body[key.toString()] = val.toString(); + } + }); + + req = req.copyWith(body: body); + } + + return req; +} From 31c3967c426b2692b4b09857c0480d417d4a2e86 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Fri, 1 Sep 2023 07:06:33 +0100 Subject: [PATCH 098/102] :label: add topics (#495) --- chopper/pubspec.yaml | 6 ++++++ chopper_built_value/pubspec.yaml | 5 +++++ chopper_generator/pubspec.yaml | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 0ddea437..4db86721 100644 --- a/chopper/pubspec.yaml +++ b/chopper/pubspec.yaml @@ -30,3 +30,9 @@ dev_dependencies: dependency_overrides: chopper_generator: path: ../chopper_generator + +topics: + - api + - client + - http + - rest \ No newline at end of file diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index b23d0a66..19eb9227 100644 --- a/chopper_built_value/pubspec.yaml +++ b/chopper_built_value/pubspec.yaml @@ -23,3 +23,8 @@ dev_dependencies: dependency_overrides: chopper: path: ../chopper + +topics: + - codegen + - converter + - built_value \ No newline at end of file diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 6e2d7d4c..a4691a32 100644 --- a/chopper_generator/pubspec.yaml +++ b/chopper_generator/pubspec.yaml @@ -28,3 +28,9 @@ dev_dependencies: dependency_overrides: chopper: path: ../chopper + +topics: + - api + - codegen + - http + - rest From a0f106d67fcf9448a0907f7dc60b47a5807d6720 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 2 Sep 2023 08:12:45 +0100 Subject: [PATCH 099/102] :bug: fix chopper base.dart send() authenticate sending wrong request (#497) --- chopper/lib/src/base.dart | 7 ++----- chopper/lib/src/interceptor.dart | 19 ++++++++++++++++--- chopper/test/converter_test.dart | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/chopper/lib/src/base.dart b/chopper/lib/src/base.dart index e791c13b..504590e2 100644 --- a/chopper/lib/src/base.dart +++ b/chopper/lib/src/base.dart @@ -309,11 +309,8 @@ base class ChopperClient { dynamic res = Response(response, response.body); if (authenticator != null) { - final Request? updatedRequest = await authenticator!.authenticate( - request, - res, - request, - ); + final Request? updatedRequest = + await authenticator!.authenticate(req, res, request); if (updatedRequest != null) { res = await send( diff --git a/chopper/lib/src/interceptor.dart b/chopper/lib/src/interceptor.dart index 1431fc23..0b11f53f 100644 --- a/chopper/lib/src/interceptor.dart +++ b/chopper/lib/src/interceptor.dart @@ -193,9 +193,12 @@ class JsonConverter implements Converter, ErrorConverter { Request encodeJson(Request request) { final String? contentType = request.headers[contentTypeKey]; - return (contentType?.contains(jsonHeaders) ?? false) - ? request.copyWith(body: json.encode(request.body)) - : request; + if ((contentType?.contains(jsonHeaders) ?? false) && + (request.body.runtimeType != String || !isJson(request.body))) { + return request.copyWith(body: json.encode(request.body)); + } + + return request; } FutureOr decodeJson(Response response) async { @@ -255,6 +258,16 @@ class JsonConverter implements Converter, ErrorConverter { static Request requestFactory(Request request) => const JsonConverter().convertRequest(request); + + @visibleForTesting + static bool isJson(dynamic data) { + try { + json.decode(data); + return true; + } catch (_) { + return false; + } + } } /// A [Converter] implementation that converts only [Request]s having a [Map] as their body. diff --git a/chopper/test/converter_test.dart b/chopper/test/converter_test.dart index d02d0a81..e1b44d33 100644 --- a/chopper/test/converter_test.dart +++ b/chopper/test/converter_test.dart @@ -111,6 +111,25 @@ void main() { expect(converted.body, equals({'foo': 'bar'})); }); + + test('JsonConverter.isJson', () { + expect(JsonConverter.isJson('{"foo":"bar"}'), isTrue); + expect(JsonConverter.isJson('foo'), isFalse); + expect(JsonConverter.isJson(''), isFalse); + expect(JsonConverter.isJson(null), isFalse); + expect(JsonConverter.isJson(42), isFalse); + expect(JsonConverter.isJson([]), isFalse); + expect(JsonConverter.isJson([1, 2, 3]), isFalse); + expect(JsonConverter.isJson(['a', 'b', 'c']), isFalse); + expect(JsonConverter.isJson({}), isFalse); + expect( + JsonConverter.isJson({ + 'foo': 'bar', + 'list': [1, 2, 3], + }), + isFalse, + ); + }); }); test('respects content-type headers', () { From f85eacd5dec9ff52ae07d710ed058c79faeeb6f3 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sat, 2 Sep 2023 13:00:07 +0100 Subject: [PATCH 100/102] :twisted_rightwards_arrows: post release branch sync (#499) --- .github/workflows/publish.yml | 13 +++++++++++++ .github/workflows/publish_dry_run.yml | 13 +++++++++++++ chopper/CHANGELOG.md | 6 ++++++ chopper/pubspec.yaml | 2 +- chopper_built_value/CHANGELOG.md | 8 ++++++++ chopper_built_value/pubspec.yaml | 4 ++-- chopper_generator/CHANGELOG.md | 5 +++++ chopper_generator/pubspec.yaml | 2 +- 8 files changed, 49 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a9da77ad..32ee9bc4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -68,6 +68,19 @@ jobs: dart pub get echo "IS_VERSION_GREATER=$(dart run compare_versions.dart $THIS_VERSION $BASE_VERSION_${{ matrix.package }})" >> $GITHUB_ENV popd || exit + - name: Validate pub.dev topics + id: validate_pub_dev_topics + run: | + set -e + pushd ${{ matrix.package }} || exit + pattern="^[a-z][a-z0-9-]*[a-z0-9]$" + for topic in $(yq -r '.topics[]' pubspec.yaml); do + if [[ ! $topic =~ $pattern ]]; then + echo "Invalid topic: $topic" + exit 1 + fi + done + popd || exit - name: Set up pub credentials id: credentials if: ${{ env.IS_VERSION_GREATER == 1 }} diff --git a/.github/workflows/publish_dry_run.yml b/.github/workflows/publish_dry_run.yml index 6ab66c84..6095578e 100644 --- a/.github/workflows/publish_dry_run.yml +++ b/.github/workflows/publish_dry_run.yml @@ -67,6 +67,19 @@ jobs: dart pub get echo "IS_VERSION_GREATER=$(dart run compare_versions.dart $THIS_VERSION $BASE_VERSION_${{ matrix.package }})" >> $GITHUB_ENV popd || exit + - name: Validate pub.dev topics + id: validate_pub_dev_topics + run: | + set -e + pushd ${{ matrix.package }} || exit + pattern="^[a-z][a-z0-9-]*[a-z0-9]$" + for topic in $(yq -r '.topics[]' pubspec.yaml); do + if [[ ! $topic =~ $pattern ]]; then + echo "Invalid topic: $topic" + exit 1 + fi + done + popd || exit - name: Publish (dry run) id: publish_dry_run if: ${{ env.IS_VERSION_GREATER == 1 }} diff --git a/chopper/CHANGELOG.md b/chopper/CHANGELOG.md index d820ae48..a2753721 100644 --- a/chopper/CHANGELOG.md +++ b/chopper/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 7.0.6 + +- The @ChopperApi annotation's baseUrl property can be used as a top level constant string variable ([#493](https://github.com/lejard-h/chopper/pull/493)) +- Fix ChopperClient.send() sending wrong request when using an Authenticator ([#497](https://github.com/lejard-h/chopper/pull/497)) +- Add pub.dev topics to package metadata ([#495](https://github.com/lejard-h/chopper/pull/495)) + ## 7.0.5 - Fix documentation links in README ([#488](https://github.com/lejard-h/chopper/pull/488)) diff --git a/chopper/pubspec.yaml b/chopper/pubspec.yaml index 4db86721..6ec0d139 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: 7.0.5 +version: 7.0.6 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 7e536f4d..16e4dacf 100644 --- a/chopper_built_value/CHANGELOG.md +++ b/chopper_built_value/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.0.1+1 + +- Fix pub.dev topic in package metadata ([#498](https://github.com/lejard-h/chopper/pull/498)) + +## 2.0.1 + +- Add pub.dev topics to package metadata ([#495](https://github.com/lejard-h/chopper/pull/495)) + ## 2.0.0 - Require Dart 3.0 or later diff --git a/chopper_built_value/pubspec.yaml b/chopper_built_value/pubspec.yaml index 19eb9227..1b15b2b0 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: 2.0.0 +version: 2.0.1+1 documentation: https://hadrien-lejard.gitbook.io/chopper/converters/built-value-converter repository: https://github.com/lejard-h/chopper @@ -27,4 +27,4 @@ dependency_overrides: topics: - codegen - converter - - built_value \ No newline at end of file + - built-value \ No newline at end of file diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 55018b6f..8697a281 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 7.0.3 + +- The @ChopperApi annotation's baseUrl property can be used as a top level constant string variable ([#493](https://github.com/lejard-h/chopper/pull/493)) +- Add pub.dev topics to package metadata ([#495](https://github.com/lejard-h/chopper/pull/495)) + ## 7.0.2 - Update analyzer dependency to >=5.13.0 <7.0.0 ([#484](https://github.com/lejard-h/chopper/pull/484)) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index a4691a32..4a10721f 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: 7.0.2 +version: 7.0.3 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper From be71064fd715187f8319b6612bf5b3728c2b92b2 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Tue, 5 Sep 2023 09:09:11 +0100 Subject: [PATCH 101/102] :rotating_light: ignore unnecessary_string_interpolations (#501) --- chopper/example/definition.chopper.dart | 2 +- chopper/test/test_service.chopper.dart | 2 +- chopper/test/test_service_variable.chopper.dart | 2 +- chopper_generator/lib/src/generator.dart | 1 + chopper_generator/test/test_service.chopper.dart | 2 +- chopper_generator/test/test_service_variable.chopper.dart | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chopper/example/definition.chopper.dart b/chopper/example/definition.chopper.dart index a7fdf51a..a2684115 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, unnecessary_brace_in_string_interps +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_string_interpolations, unnecessary_brace_in_string_interps final class _$MyService extends MyService { _$MyService([ChopperClient? client]) { if (client == null) return; diff --git a/chopper/test/test_service.chopper.dart b/chopper/test/test_service.chopper.dart index d0379988..4df2dd03 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, unnecessary_brace_in_string_interps +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_string_interpolations, unnecessary_brace_in_string_interps final class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; diff --git a/chopper/test/test_service_variable.chopper.dart b/chopper/test/test_service_variable.chopper.dart index c4423e71..038868a7 100644 --- a/chopper/test/test_service_variable.chopper.dart +++ b/chopper/test/test_service_variable.chopper.dart @@ -6,7 +6,7 @@ part of 'test_service_variable.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_string_interpolations, unnecessary_brace_in_string_interps final class _$HttpTestServiceVariable extends HttpTestServiceVariable { _$HttpTestServiceVariable([ChopperClient? client]) { if (client == null) return; diff --git a/chopper_generator/lib/src/generator.dart b/chopper_generator/lib/src/generator.dart index 81518109..e194f52c 100644 --- a/chopper_generator/lib/src/generator.dart +++ b/chopper_generator/lib/src/generator.dart @@ -97,6 +97,7 @@ final class ChopperGenerator 'always_put_control_body_on_new_line, ' 'always_specify_types, ' 'prefer_const_declarations, ' + 'unnecessary_string_interpolations, ' 'unnecessary_brace_in_string_interps'; final DartEmitter emitter = DartEmitter(); diff --git a/chopper_generator/test/test_service.chopper.dart b/chopper_generator/test/test_service.chopper.dart index d0379988..4df2dd03 100644 --- a/chopper_generator/test/test_service.chopper.dart +++ b/chopper_generator/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, unnecessary_brace_in_string_interps +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_string_interpolations, unnecessary_brace_in_string_interps final class _$HttpTestService extends HttpTestService { _$HttpTestService([ChopperClient? client]) { if (client == null) return; diff --git a/chopper_generator/test/test_service_variable.chopper.dart b/chopper_generator/test/test_service_variable.chopper.dart index c4423e71..038868a7 100644 --- a/chopper_generator/test/test_service_variable.chopper.dart +++ b/chopper_generator/test/test_service_variable.chopper.dart @@ -6,7 +6,7 @@ part of 'test_service_variable.dart'; // ChopperGenerator // ************************************************************************** -// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_brace_in_string_interps +// ignore_for_file: always_put_control_body_on_new_line, always_specify_types, prefer_const_declarations, unnecessary_string_interpolations, unnecessary_brace_in_string_interps final class _$HttpTestServiceVariable extends HttpTestServiceVariable { _$HttpTestServiceVariable([ChopperClient? client]) { if (client == null) return; From e14141511e663f2639649ad0c652001f0bde9d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klemen=20Tu=C5=A1ar?= Date: Tue, 5 Sep 2023 18:57:02 +0100 Subject: [PATCH 102/102] :bookmark: release chopper_generator v7.0.4 # chopper_generator ## 7.0.4 - #501 --- chopper_generator/CHANGELOG.md | 4 ++++ chopper_generator/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chopper_generator/CHANGELOG.md b/chopper_generator/CHANGELOG.md index 8697a281..0b7c9f39 100644 --- a/chopper_generator/CHANGELOG.md +++ b/chopper_generator/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 7.0.4 + +- Ignore unnecessary_string_interpolations ([#501](https://github.com/lejard-h/chopper/pull/501)) + ## 7.0.3 - The @ChopperApi annotation's baseUrl property can be used as a top level constant string variable ([#493](https://github.com/lejard-h/chopper/pull/493)) diff --git a/chopper_generator/pubspec.yaml b/chopper_generator/pubspec.yaml index 4a10721f..5cfb749d 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: 7.0.3 +version: 7.0.4 documentation: https://hadrien-lejard.gitbook.io/chopper repository: https://github.com/lejard-h/chopper