From 873a581df863a6af7d4374412d39d715559a7f6d Mon Sep 17 00:00:00 2001 From: jld3103 Date: Wed, 21 Sep 2022 18:52:46 +0200 Subject: [PATCH] dynamite: Init --- .github/workflows/dart.yml | 352 +++-- packages/dynamite/.gitignore | 10 + packages/dynamite/analysis_options.yaml | 9 + packages/dynamite/build.yaml | 22 + packages/dynamite/lib/builder.dart | 4 + packages/dynamite/lib/dynamite.dart | 15 + .../dynamite/lib/src/models/components.dart | 20 + .../dynamite/lib/src/models/components.g.dart | 36 + packages/dynamite/lib/src/models/info.dart | 25 + packages/dynamite/lib/src/models/info.g.dart | 37 + packages/dynamite/lib/src/models/license.dart | 21 + .../dynamite/lib/src/models/license.g.dart | 35 + .../dynamite/lib/src/models/media_type.dart | 16 + .../dynamite/lib/src/models/media_type.g.dart | 30 + .../dynamite/lib/src/models/open_api.dart | 41 + .../dynamite/lib/src/models/open_api.g.dart | 49 + .../dynamite/lib/src/models/operation.dart | 31 + .../dynamite/lib/src/models/operation.g.dart | 41 + .../dynamite/lib/src/models/parameter.dart | 29 + .../dynamite/lib/src/models/parameter.g.dart | 39 + .../dynamite/lib/src/models/path_item.dart | 68 + .../dynamite/lib/src/models/path_item.g.dart | 60 + packages/dynamite/lib/src/models/paths.dart | 3 + .../dynamite/lib/src/models/request_body.dart | 22 + .../lib/src/models/request_body.g.dart | 36 + .../dynamite/lib/src/models/response.dart | 19 + .../dynamite/lib/src/models/response.g.dart | 35 + .../dynamite/lib/src/models/responses.dart | 3 + packages/dynamite/lib/src/models/schema.dart | 53 + .../dynamite/lib/src/models/schema.g.dart | 67 + .../lib/src/models/security_requirement.dart | 1 + .../lib/src/models/security_scheme.dart | 19 + .../lib/src/models/security_scheme.g.dart | 35 + packages/dynamite/lib/src/models/server.dart | 19 + .../dynamite/lib/src/models/server.g.dart | 35 + .../lib/src/models/server_variable.dart | 23 + .../lib/src/models/server_variable.g.dart | 35 + packages/dynamite/lib/src/models/tag.dart | 15 + packages/dynamite/lib/src/models/tag.g.dart | 21 + .../dynamite/lib/src/openapi_builder.dart | 1244 +++++++++++++++++ packages/dynamite/mono_pkg.yaml | 7 + packages/dynamite/pubspec.yaml | 20 + tool/.gitattributes | 1 + tool/ci.sh | 8 +- 44 files changed, 2565 insertions(+), 146 deletions(-) create mode 100644 packages/dynamite/.gitignore create mode 100644 packages/dynamite/analysis_options.yaml create mode 100644 packages/dynamite/build.yaml create mode 100644 packages/dynamite/lib/builder.dart create mode 100644 packages/dynamite/lib/dynamite.dart create mode 100644 packages/dynamite/lib/src/models/components.dart create mode 100644 packages/dynamite/lib/src/models/components.g.dart create mode 100644 packages/dynamite/lib/src/models/info.dart create mode 100644 packages/dynamite/lib/src/models/info.g.dart create mode 100644 packages/dynamite/lib/src/models/license.dart create mode 100644 packages/dynamite/lib/src/models/license.g.dart create mode 100644 packages/dynamite/lib/src/models/media_type.dart create mode 100644 packages/dynamite/lib/src/models/media_type.g.dart create mode 100644 packages/dynamite/lib/src/models/open_api.dart create mode 100644 packages/dynamite/lib/src/models/open_api.g.dart create mode 100644 packages/dynamite/lib/src/models/operation.dart create mode 100644 packages/dynamite/lib/src/models/operation.g.dart create mode 100644 packages/dynamite/lib/src/models/parameter.dart create mode 100644 packages/dynamite/lib/src/models/parameter.g.dart create mode 100644 packages/dynamite/lib/src/models/path_item.dart create mode 100644 packages/dynamite/lib/src/models/path_item.g.dart create mode 100644 packages/dynamite/lib/src/models/paths.dart create mode 100644 packages/dynamite/lib/src/models/request_body.dart create mode 100644 packages/dynamite/lib/src/models/request_body.g.dart create mode 100644 packages/dynamite/lib/src/models/response.dart create mode 100644 packages/dynamite/lib/src/models/response.g.dart create mode 100644 packages/dynamite/lib/src/models/responses.dart create mode 100644 packages/dynamite/lib/src/models/schema.dart create mode 100644 packages/dynamite/lib/src/models/schema.g.dart create mode 100644 packages/dynamite/lib/src/models/security_requirement.dart create mode 100644 packages/dynamite/lib/src/models/security_scheme.dart create mode 100644 packages/dynamite/lib/src/models/security_scheme.g.dart create mode 100644 packages/dynamite/lib/src/models/server.dart create mode 100644 packages/dynamite/lib/src/models/server.g.dart create mode 100644 packages/dynamite/lib/src/models/server_variable.dart create mode 100644 packages/dynamite/lib/src/models/server_variable.g.dart create mode 100644 packages/dynamite/lib/src/models/tag.dart create mode 100644 packages/dynamite/lib/src/models/tag.g.dart create mode 100644 packages/dynamite/lib/src/openapi_builder.dart create mode 100644 packages/dynamite/mono_pkg.yaml create mode 100644 packages/dynamite/pubspec.yaml create mode 100644 tool/.gitattributes diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 70b8548f..1c988d92 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -37,6 +37,198 @@ jobs: - name: mono_repo self validate run: dart pub global run mono_repo generate --validate job_002: + name: "analyze; PKG: packages/dynamite; `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/dynamite;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/dynamite + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_dynamite_pub_upgrade + name: packages/dynamite; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/dynamite + - name: "packages/dynamite; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.packages_dynamite_pub_upgrade.conclusion == 'success'" + working-directory: packages/dynamite + needs: + - job_001 + job_003: + name: "analyze; PKG: packages/nextcloud; `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_nextcloud_pub_upgrade + name: packages/nextcloud; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/nextcloud + - name: "packages/nextcloud; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.packages_nextcloud_pub_upgrade.conclusion == 'success'" + working-directory: packages/nextcloud + needs: + - job_001 + job_004: + name: "analyze; PKG: packages/nextcloud_push_proxy; `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_nextcloud_push_proxy_pub_upgrade + name: packages/nextcloud_push_proxy; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/nextcloud_push_proxy + - name: "packages/nextcloud_push_proxy; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.packages_nextcloud_push_proxy_pub_upgrade.conclusion == 'success'" + working-directory: packages/nextcloud_push_proxy + needs: + - job_001 + job_005: + name: "analyze; PKG: packages/sort_box; `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/sort_box;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/sort_box + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_sort_box_pub_upgrade + name: packages/sort_box; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/sort_box + - name: "packages/sort_box; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.packages_sort_box_pub_upgrade.conclusion == 'success'" + working-directory: packages/sort_box + needs: + - job_001 + job_006: + name: "analyze; PKG: packages/spec_templates; `dart analyze --fatal-infos .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/spec_templates;commands:analyze_0" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/spec_templates + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_spec_templates_pub_upgrade + name: packages/spec_templates; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/spec_templates + - name: "packages/spec_templates; dart analyze --fatal-infos ." + run: dart analyze --fatal-infos . + if: "always() && steps.packages_spec_templates_pub_upgrade.conclusion == 'success'" + working-directory: packages/spec_templates + needs: + - job_001 + job_007: + name: "analyze; PKG: packages/dynamite; `dart format --output=none --set-exit-if-changed --line-length 120 .`" + runs-on: ubuntu-latest + steps: + - name: Cache Pub hosted dependencies + uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 + with: + path: "~/.pub-cache/hosted" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/dynamite;commands:format" + restore-keys: | + os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/dynamite + 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@2541b1294d2704b0964813337f33b291d3f8596b + - id: packages_dynamite_pub_upgrade + name: packages/dynamite; dart pub upgrade + run: dart pub upgrade + if: "always() && steps.checkout.conclusion == 'success'" + working-directory: packages/dynamite + - name: "packages/dynamite; dart format --output=none --set-exit-if-changed --line-length 120 ." + run: "dart format --output=none --set-exit-if-changed --line-length 120 ." + if: "always() && steps.packages_dynamite_pub_upgrade.conclusion == 'success'" + working-directory: packages/dynamite + needs: + - job_001 + job_008: name: "analyze; PKG: packages/file_icons; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -68,7 +260,7 @@ jobs: working-directory: packages/file_icons needs: - job_001 - job_003: + job_009: name: "analyze; PKG: packages/neon; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -100,7 +292,7 @@ jobs: working-directory: packages/neon needs: - job_001 - job_004: + job_010: name: "analyze; PKG: packages/nextcloud; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -132,7 +324,7 @@ jobs: working-directory: packages/nextcloud needs: - job_001 - job_005: + job_011: name: "analyze; PKG: packages/nextcloud_push_proxy; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -164,7 +356,7 @@ jobs: working-directory: packages/nextcloud_push_proxy needs: - job_001 - job_006: + job_012: name: "analyze; PKG: packages/settings; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -196,7 +388,7 @@ jobs: working-directory: packages/settings needs: - job_001 - job_007: + job_013: name: "analyze; PKG: packages/sort_box; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -228,7 +420,7 @@ jobs: working-directory: packages/sort_box needs: - job_001 - job_008: + job_014: name: "analyze; PKG: packages/spec_templates; `dart format --output=none --set-exit-if-changed --line-length 120 .`" runs-on: ubuntu-latest steps: @@ -260,7 +452,7 @@ jobs: working-directory: packages/spec_templates needs: - job_001 - job_009: + job_015: name: "analyze; PKG: packages/file_icons; `flutter analyze --fatal-infos .`" runs-on: ubuntu-latest steps: @@ -268,7 +460,7 @@ jobs: uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/file_icons;commands:analyze_0" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/file_icons;commands:analyze_1" restore-keys: | os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/file_icons os:ubuntu-latest;pub-cache-hosted;sdk:stable @@ -292,7 +484,7 @@ jobs: working-directory: packages/file_icons needs: - job_001 - job_010: + job_016: name: "analyze; PKG: packages/neon; `flutter analyze --fatal-infos .`" runs-on: ubuntu-latest steps: @@ -300,7 +492,7 @@ jobs: uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/neon;commands:analyze_0" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/neon;commands:analyze_1" restore-keys: | os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/neon os:ubuntu-latest;pub-cache-hosted;sdk:stable @@ -324,7 +516,7 @@ jobs: working-directory: packages/neon needs: - job_001 - job_011: + job_017: name: "analyze; PKG: packages/settings; `flutter analyze --fatal-infos .`" runs-on: ubuntu-latest steps: @@ -332,7 +524,7 @@ jobs: uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 with: path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/settings;commands:analyze_0" + key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/settings;commands:analyze_1" restore-keys: | os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/settings os:ubuntu-latest;pub-cache-hosted;sdk:stable @@ -356,135 +548,7 @@ jobs: working-directory: packages/settings needs: - job_001 - job_012: - name: "analyze; PKG: packages/nextcloud; `dart analyze --fatal-infos .`" - runs-on: ubuntu-latest - steps: - - name: Cache Pub hosted dependencies - uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 - with: - path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud;commands:analyze_1" - restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud - 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@2541b1294d2704b0964813337f33b291d3f8596b - - id: packages_nextcloud_pub_upgrade - name: packages/nextcloud; dart pub upgrade - run: dart pub upgrade - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: packages/nextcloud - - name: "packages/nextcloud; dart analyze --fatal-infos ." - run: dart analyze --fatal-infos . - if: "always() && steps.packages_nextcloud_pub_upgrade.conclusion == 'success'" - working-directory: packages/nextcloud - needs: - - job_001 - job_013: - name: "analyze; PKG: packages/nextcloud_push_proxy; `dart analyze --fatal-infos .`" - runs-on: ubuntu-latest - steps: - - name: Cache Pub hosted dependencies - uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 - with: - path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy;commands:analyze_1" - restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/nextcloud_push_proxy - 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@2541b1294d2704b0964813337f33b291d3f8596b - - id: packages_nextcloud_push_proxy_pub_upgrade - name: packages/nextcloud_push_proxy; dart pub upgrade - run: dart pub upgrade - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: packages/nextcloud_push_proxy - - name: "packages/nextcloud_push_proxy; dart analyze --fatal-infos ." - run: dart analyze --fatal-infos . - if: "always() && steps.packages_nextcloud_push_proxy_pub_upgrade.conclusion == 'success'" - working-directory: packages/nextcloud_push_proxy - needs: - - job_001 - job_014: - name: "analyze; PKG: packages/sort_box; `dart analyze --fatal-infos .`" - runs-on: ubuntu-latest - steps: - - name: Cache Pub hosted dependencies - uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 - with: - path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/sort_box;commands:analyze_1" - restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/sort_box - 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@2541b1294d2704b0964813337f33b291d3f8596b - - id: packages_sort_box_pub_upgrade - name: packages/sort_box; dart pub upgrade - run: dart pub upgrade - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: packages/sort_box - - name: "packages/sort_box; dart analyze --fatal-infos ." - run: dart analyze --fatal-infos . - if: "always() && steps.packages_sort_box_pub_upgrade.conclusion == 'success'" - working-directory: packages/sort_box - needs: - - job_001 - job_015: - name: "analyze; PKG: packages/spec_templates; `dart analyze --fatal-infos .`" - runs-on: ubuntu-latest - steps: - - name: Cache Pub hosted dependencies - uses: actions/cache@fd5de65bc895cf536527842281bea11763fefd77 - with: - path: "~/.pub-cache/hosted" - key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/spec_templates;commands:analyze_1" - restore-keys: | - os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:packages/spec_templates - 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@2541b1294d2704b0964813337f33b291d3f8596b - - id: packages_spec_templates_pub_upgrade - name: packages/spec_templates; dart pub upgrade - run: dart pub upgrade - if: "always() && steps.checkout.conclusion == 'success'" - working-directory: packages/spec_templates - - name: "packages/spec_templates; dart analyze --fatal-infos ." - run: dart analyze --fatal-infos . - if: "always() && steps.packages_spec_templates_pub_upgrade.conclusion == 'success'" - working-directory: packages/spec_templates - needs: - - job_001 - job_016: + job_018: name: "unit_test; PKG: packages/nextcloud; `dart test`" runs-on: ubuntu-latest steps: @@ -530,7 +594,9 @@ jobs: - job_013 - job_014 - job_015 - job_017: + - job_016 + - job_017 + job_019: name: "unit_test; PKG: packages/sort_box; `dart test`" runs-on: ubuntu-latest steps: @@ -576,3 +642,5 @@ jobs: - job_013 - job_014 - job_015 + - job_016 + - job_017 diff --git a/packages/dynamite/.gitignore b/packages/dynamite/.gitignore new file mode 100644 index 00000000..65c34dc8 --- /dev/null +++ b/packages/dynamite/.gitignore @@ -0,0 +1,10 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build outputs. +build/ + +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/dynamite/analysis_options.yaml b/packages/dynamite/analysis_options.yaml new file mode 100644 index 00000000..6549e762 --- /dev/null +++ b/packages/dynamite/analysis_options.yaml @@ -0,0 +1,9 @@ +include: package:nit_picking/dart.yaml + +linter: + rules: + public_member_api_docs: false + +analyzer: + exclude: + - '**.g.dart' diff --git a/packages/dynamite/build.yaml b/packages/dynamite/build.yaml new file mode 100644 index 00000000..73fa6bce --- /dev/null +++ b/packages/dynamite/build.yaml @@ -0,0 +1,22 @@ +targets: + $default: + builders: + json_serializable: + options: + disallow_unrecognized_keys: true + explicit_to_json: true + create_factory: true + create_to_json: true + include_if_null: false + dynamite: + enabled: true + +builders: + dynamite: + import: "package:dynamite/builder.dart" + builder_factories: ["openAPIBuilder"] + build_extensions: {'.openapi.json': ['openapi.dart']} + auto_apply: root_package + build_to: source + runs_before: ["json_serializable"] + applies_builders: ["json_serializable"] diff --git a/packages/dynamite/lib/builder.dart b/packages/dynamite/lib/builder.dart new file mode 100644 index 00000000..18157106 --- /dev/null +++ b/packages/dynamite/lib/builder.dart @@ -0,0 +1,4 @@ +import 'package:build/build.dart'; +import 'package:dynamite/dynamite.dart'; + +Builder openAPIBuilder(final BuilderOptions options) => OpenAPIBuilder(); diff --git a/packages/dynamite/lib/dynamite.dart b/packages/dynamite/lib/dynamite.dart new file mode 100644 index 00000000..2e59b2df --- /dev/null +++ b/packages/dynamite/lib/dynamite.dart @@ -0,0 +1,15 @@ +library dynamite; + +import 'dart:convert'; + +import 'package:build/build.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:dynamite/src/models/open_api.dart'; +import 'package:dynamite/src/models/parameter.dart' as spec_parameter; +import 'package:dynamite/src/models/path_item.dart'; +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/models/tag.dart'; +import 'package:path/path.dart' as p; + +part 'src/openapi_builder.dart'; diff --git a/packages/dynamite/lib/src/models/components.dart b/packages/dynamite/lib/src/models/components.dart new file mode 100644 index 00000000..2796da62 --- /dev/null +++ b/packages/dynamite/lib/src/models/components.dart @@ -0,0 +1,20 @@ +import 'package:dynamite/src/models/schema.dart'; +import 'package:dynamite/src/models/security_scheme.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'components.g.dart'; + +@JsonSerializable() +class Components { + Components({ + required this.securitySchemes, + required this.schemas, + }); + + factory Components.fromJson(final Map json) => _$ComponentsFromJson(json); + Map toJson() => _$ComponentsToJson(this); + + final Map? securitySchemes; + + final Map? schemas; +} diff --git a/packages/dynamite/lib/src/models/components.g.dart b/packages/dynamite/lib/src/models/components.g.dart new file mode 100644 index 00000000..8144f459 --- /dev/null +++ b/packages/dynamite/lib/src/models/components.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'components.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Components _$ComponentsFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['securitySchemes', 'schemas'], + ); + return Components( + securitySchemes: (json['securitySchemes'] as Map?)?.map( + (k, e) => MapEntry(k, SecurityScheme.fromJson(e as Map)), + ), + schemas: (json['schemas'] as Map?)?.map( + (k, e) => MapEntry(k, Schema.fromJson(e as Map)), + ), + ); +} + +Map _$ComponentsToJson(Components instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('securitySchemes', instance.securitySchemes?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('schemas', instance.schemas?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/dynamite/lib/src/models/info.dart b/packages/dynamite/lib/src/models/info.dart new file mode 100644 index 00000000..ff727f2b --- /dev/null +++ b/packages/dynamite/lib/src/models/info.dart @@ -0,0 +1,25 @@ +import 'package:dynamite/src/models/license.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'info.g.dart'; + +@JsonSerializable() +class Info { + Info({ + required this.title, + required this.version, + required this.description, + required this.license, + }); + + factory Info.fromJson(final Map json) => _$InfoFromJson(json); + Map toJson() => _$InfoToJson(this); + + final String title; + + final String version; + + final String? description; + + final License license; +} diff --git a/packages/dynamite/lib/src/models/info.g.dart b/packages/dynamite/lib/src/models/info.g.dart new file mode 100644 index 00000000..84e3239b --- /dev/null +++ b/packages/dynamite/lib/src/models/info.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'info.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Info _$InfoFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['title', 'version', 'description', 'license'], + ); + return Info( + title: json['title'] as String, + version: json['version'] as String, + description: json['description'] as String?, + license: License.fromJson(json['license'] as Map), + ); +} + +Map _$InfoToJson(Info instance) { + final val = { + 'title': instance.title, + 'version': instance.version, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('description', instance.description); + val['license'] = instance.license.toJson(); + return val; +} diff --git a/packages/dynamite/lib/src/models/license.dart b/packages/dynamite/lib/src/models/license.dart new file mode 100644 index 00000000..00dbb0cd --- /dev/null +++ b/packages/dynamite/lib/src/models/license.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'license.g.dart'; + +@JsonSerializable() +class License { + License({ + required this.name, + required this.identifier, + required this.url, + }); + + factory License.fromJson(final Map json) => _$LicenseFromJson(json); + Map toJson() => _$LicenseToJson(this); + + final String name; + + final String? identifier; + + final String? url; +} diff --git a/packages/dynamite/lib/src/models/license.g.dart b/packages/dynamite/lib/src/models/license.g.dart new file mode 100644 index 00000000..1a0d0d49 --- /dev/null +++ b/packages/dynamite/lib/src/models/license.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'license.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +License _$LicenseFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['name', 'identifier', 'url'], + ); + return License( + name: json['name'] as String, + identifier: json['identifier'] as String?, + url: json['url'] as String?, + ); +} + +Map _$LicenseToJson(License instance) { + final val = { + 'name': instance.name, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('identifier', instance.identifier); + writeNotNull('url', instance.url); + return val; +} diff --git a/packages/dynamite/lib/src/models/media_type.dart b/packages/dynamite/lib/src/models/media_type.dart new file mode 100644 index 00000000..798ad5d9 --- /dev/null +++ b/packages/dynamite/lib/src/models/media_type.dart @@ -0,0 +1,16 @@ +import 'package:dynamite/src/models/schema.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'media_type.g.dart'; + +@JsonSerializable() +class MediaType { + MediaType({ + required this.schema, + }); + + factory MediaType.fromJson(final Map json) => _$MediaTypeFromJson(json); + Map toJson() => _$MediaTypeToJson(this); + + final Schema? schema; +} diff --git a/packages/dynamite/lib/src/models/media_type.g.dart b/packages/dynamite/lib/src/models/media_type.g.dart new file mode 100644 index 00000000..4c838347 --- /dev/null +++ b/packages/dynamite/lib/src/models/media_type.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'media_type.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MediaType _$MediaTypeFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['schema'], + ); + return MediaType( + schema: json['schema'] == null ? null : Schema.fromJson(json['schema'] as Map), + ); +} + +Map _$MediaTypeToJson(MediaType instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('schema', instance.schema?.toJson()); + return val; +} diff --git a/packages/dynamite/lib/src/models/open_api.dart b/packages/dynamite/lib/src/models/open_api.dart new file mode 100644 index 00000000..08c51755 --- /dev/null +++ b/packages/dynamite/lib/src/models/open_api.dart @@ -0,0 +1,41 @@ +import 'package:dynamite/src/models/components.dart'; +import 'package:dynamite/src/models/info.dart'; +import 'package:dynamite/src/models/path_item.dart'; +import 'package:dynamite/src/models/paths.dart'; +import 'package:dynamite/src/models/security_requirement.dart'; +import 'package:dynamite/src/models/server.dart'; +import 'package:dynamite/src/models/tag.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'open_api.g.dart'; + +@JsonSerializable() +class OpenAPI { + OpenAPI({ + required this.version, + required this.info, + required this.servers, + required this.security, + required this.tags, + required this.components, + required this.paths, + }); + + factory OpenAPI.fromJson(final Map json) => _$OpenAPIFromJson(json); + Map toJson() => _$OpenAPIToJson(this); + + @JsonKey(name: 'openapi') + final String version; + + final Info info; + + final List? servers; + + final List? security; + + final List? tags; + + final Components? components; + + final Paths? paths; +} diff --git a/packages/dynamite/lib/src/models/open_api.g.dart b/packages/dynamite/lib/src/models/open_api.g.dart new file mode 100644 index 00000000..36cfe287 --- /dev/null +++ b/packages/dynamite/lib/src/models/open_api.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'open_api.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +OpenAPI _$OpenAPIFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['openapi', 'info', 'servers', 'security', 'tags', 'components', 'paths'], + ); + return OpenAPI( + version: json['openapi'] as String, + info: Info.fromJson(json['info'] as Map), + servers: (json['servers'] as List?)?.map((e) => Server.fromJson(e as Map)).toList(), + security: (json['security'] as List?) + ?.map((e) => (e as Map).map( + (k, e) => MapEntry(k, (e as List).map((e) => e as String).toList()), + )) + .toList(), + tags: (json['tags'] as List?)?.map((e) => Tag.fromJson(e as Map)).toList(), + components: json['components'] == null ? null : Components.fromJson(json['components'] as Map), + paths: (json['paths'] as Map?)?.map( + (k, e) => MapEntry(k, PathItem.fromJson(e as Map)), + ), + ); +} + +Map _$OpenAPIToJson(OpenAPI instance) { + final val = { + 'openapi': instance.version, + 'info': instance.info.toJson(), + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('servers', instance.servers?.map((e) => e.toJson()).toList()); + writeNotNull('security', instance.security); + writeNotNull('tags', instance.tags?.map((e) => e.toJson()).toList()); + writeNotNull('components', instance.components?.toJson()); + writeNotNull('paths', instance.paths?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/dynamite/lib/src/models/operation.dart b/packages/dynamite/lib/src/models/operation.dart new file mode 100644 index 00000000..d1590a3d --- /dev/null +++ b/packages/dynamite/lib/src/models/operation.dart @@ -0,0 +1,31 @@ +import 'package:dynamite/src/models/parameter.dart'; +import 'package:dynamite/src/models/request_body.dart'; +import 'package:dynamite/src/models/response.dart'; +import 'package:dynamite/src/models/responses.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'operation.g.dart'; + +@JsonSerializable() +class Operation { + Operation({ + required this.operationId, + required this.tags, + required this.parameters, + required this.requestBody, + required this.responses, + }); + + factory Operation.fromJson(final Map json) => _$OperationFromJson(json); + Map toJson() => _$OperationToJson(this); + + final String? operationId; + + final List? tags; + + final List? parameters; + + final RequestBody? requestBody; + + final Responses? responses; +} diff --git a/packages/dynamite/lib/src/models/operation.g.dart b/packages/dynamite/lib/src/models/operation.g.dart new file mode 100644 index 00000000..41d0abf9 --- /dev/null +++ b/packages/dynamite/lib/src/models/operation.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'operation.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Operation _$OperationFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['operationId', 'tags', 'parameters', 'requestBody', 'responses'], + ); + return Operation( + operationId: json['operationId'] as String?, + tags: (json['tags'] as List?)?.map((e) => e as String).toList(), + parameters: + (json['parameters'] as List?)?.map((e) => Parameter.fromJson(e as Map)).toList(), + requestBody: json['requestBody'] == null ? null : RequestBody.fromJson(json['requestBody'] as Map), + responses: (json['responses'] as Map?)?.map( + (k, e) => MapEntry(k, Response.fromJson(e as Map)), + ), + ); +} + +Map _$OperationToJson(Operation instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('operationId', instance.operationId); + writeNotNull('tags', instance.tags); + writeNotNull('parameters', instance.parameters?.map((e) => e.toJson()).toList()); + writeNotNull('requestBody', instance.requestBody?.toJson()); + writeNotNull('responses', instance.responses?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/dynamite/lib/src/models/parameter.dart b/packages/dynamite/lib/src/models/parameter.dart new file mode 100644 index 00000000..a3d309df --- /dev/null +++ b/packages/dynamite/lib/src/models/parameter.dart @@ -0,0 +1,29 @@ +import 'package:dynamite/src/models/schema.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'parameter.g.dart'; + +@JsonSerializable() +class Parameter { + Parameter({ + required this.name, + required this.in_, + required this.description, + required this.required, + required this.schema, + }); + + factory Parameter.fromJson(final Map json) => _$ParameterFromJson(json); + Map toJson() => _$ParameterToJson(this); + + final String name; + + @JsonKey(name: 'in') + final String in_; + + final String? description; + + final bool? required; + + final Schema? schema; +} diff --git a/packages/dynamite/lib/src/models/parameter.g.dart b/packages/dynamite/lib/src/models/parameter.g.dart new file mode 100644 index 00000000..b603a039 --- /dev/null +++ b/packages/dynamite/lib/src/models/parameter.g.dart @@ -0,0 +1,39 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'parameter.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Parameter _$ParameterFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['name', 'in', 'description', 'required', 'schema'], + ); + return Parameter( + name: json['name'] as String, + in_: json['in'] as String, + description: json['description'] as String?, + required: json['required'] as bool?, + schema: json['schema'] == null ? null : Schema.fromJson(json['schema'] as Map), + ); +} + +Map _$ParameterToJson(Parameter instance) { + final val = { + 'name': instance.name, + 'in': instance.in_, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('description', instance.description); + writeNotNull('required', instance.required); + writeNotNull('schema', instance.schema?.toJson()); + return val; +} diff --git a/packages/dynamite/lib/src/models/path_item.dart b/packages/dynamite/lib/src/models/path_item.dart new file mode 100644 index 00000000..8684f923 --- /dev/null +++ b/packages/dynamite/lib/src/models/path_item.dart @@ -0,0 +1,68 @@ +import 'package:dynamite/src/models/operation.dart'; +import 'package:dynamite/src/models/parameter.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'path_item.g.dart'; + +@JsonSerializable() +class PathItem { + PathItem({ + required this.description, + required this.parameters, + required this.get, + required this.put, + required this.post, + required this.delete, + required this.options, + required this.head, + required this.patch, + required this.trace, + }); + + factory PathItem.fromJson(final Map json) => _$PathItemFromJson(json); + Map toJson() => _$PathItemToJson(this); + + final String? description; + + final List? parameters; + + final Operation? get; + + final Operation? put; + + final Operation? post; + + final Operation? delete; + + final Operation? options; + + final Operation? head; + + final Operation? patch; + + final Operation? trace; + + Map get operations => { + if (get != null) 'get': get!, + if (put != null) 'put': put!, + if (post != null) 'post': post!, + if (delete != null) 'delete': delete!, + if (options != null) 'options': options!, + if (head != null) 'head': head!, + if (patch != null) 'patch': patch!, + if (trace != null) 'trace': trace!, + }; + + PathItem copyWithOperations(final Map operations) => PathItem( + description: description, + parameters: parameters, + get: operations['get'] ?? get, + put: operations['put'] ?? put, + post: operations['post'] ?? post, + delete: operations['delete'] ?? delete, + options: operations['options'] ?? options, + head: operations['head'] ?? head, + patch: operations['patch'] ?? patch, + trace: operations['trace'] ?? trace, + ); +} diff --git a/packages/dynamite/lib/src/models/path_item.g.dart b/packages/dynamite/lib/src/models/path_item.g.dart new file mode 100644 index 00000000..792389e7 --- /dev/null +++ b/packages/dynamite/lib/src/models/path_item.g.dart @@ -0,0 +1,60 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'path_item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PathItem _$PathItemFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const [ + 'description', + 'parameters', + 'get', + 'put', + 'post', + 'delete', + 'options', + 'head', + 'patch', + 'trace' + ], + ); + return PathItem( + description: json['description'] as String?, + parameters: + (json['parameters'] as List?)?.map((e) => Parameter.fromJson(e as Map)).toList(), + get: json['get'] == null ? null : Operation.fromJson(json['get'] as Map), + put: json['put'] == null ? null : Operation.fromJson(json['put'] as Map), + post: json['post'] == null ? null : Operation.fromJson(json['post'] as Map), + delete: json['delete'] == null ? null : Operation.fromJson(json['delete'] as Map), + options: json['options'] == null ? null : Operation.fromJson(json['options'] as Map), + head: json['head'] == null ? null : Operation.fromJson(json['head'] as Map), + patch: json['patch'] == null ? null : Operation.fromJson(json['patch'] as Map), + trace: json['trace'] == null ? null : Operation.fromJson(json['trace'] as Map), + ); +} + +Map _$PathItemToJson(PathItem instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('description', instance.description); + writeNotNull('parameters', instance.parameters?.map((e) => e.toJson()).toList()); + writeNotNull('get', instance.get?.toJson()); + writeNotNull('put', instance.put?.toJson()); + writeNotNull('post', instance.post?.toJson()); + writeNotNull('delete', instance.delete?.toJson()); + writeNotNull('options', instance.options?.toJson()); + writeNotNull('head', instance.head?.toJson()); + writeNotNull('patch', instance.patch?.toJson()); + writeNotNull('trace', instance.trace?.toJson()); + return val; +} diff --git a/packages/dynamite/lib/src/models/paths.dart b/packages/dynamite/lib/src/models/paths.dart new file mode 100644 index 00000000..2adb5d6b --- /dev/null +++ b/packages/dynamite/lib/src/models/paths.dart @@ -0,0 +1,3 @@ +import 'package:dynamite/src/models/path_item.dart'; + +typedef Paths = Map; diff --git a/packages/dynamite/lib/src/models/request_body.dart b/packages/dynamite/lib/src/models/request_body.dart new file mode 100644 index 00000000..3ed03da9 --- /dev/null +++ b/packages/dynamite/lib/src/models/request_body.dart @@ -0,0 +1,22 @@ +import 'package:dynamite/src/models/media_type.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'request_body.g.dart'; + +@JsonSerializable() +class RequestBody { + RequestBody({ + required this.description, + required this.content, + required this.required, + }); + + factory RequestBody.fromJson(final Map json) => _$RequestBodyFromJson(json); + Map toJson() => _$RequestBodyToJson(this); + + final String? description; + + final Map? content; + + final bool? required; +} diff --git a/packages/dynamite/lib/src/models/request_body.g.dart b/packages/dynamite/lib/src/models/request_body.g.dart new file mode 100644 index 00000000..f6b95111 --- /dev/null +++ b/packages/dynamite/lib/src/models/request_body.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'request_body.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RequestBody _$RequestBodyFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['description', 'content', 'required'], + ); + return RequestBody( + description: json['description'] as String?, + content: (json['content'] as Map?)?.map( + (k, e) => MapEntry(k, MediaType.fromJson(e as Map)), + ), + required: json['required'] as bool?, + ); +} + +Map _$RequestBodyToJson(RequestBody instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('description', instance.description); + writeNotNull('content', instance.content?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('required', instance.required); + return val; +} diff --git a/packages/dynamite/lib/src/models/response.dart b/packages/dynamite/lib/src/models/response.dart new file mode 100644 index 00000000..9a917517 --- /dev/null +++ b/packages/dynamite/lib/src/models/response.dart @@ -0,0 +1,19 @@ +import 'package:dynamite/src/models/media_type.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'response.g.dart'; + +@JsonSerializable() +class Response { + Response({ + required this.description, + required this.content, + }); + + factory Response.fromJson(final Map json) => _$ResponseFromJson(json); + Map toJson() => _$ResponseToJson(this); + + final String description; + + final Map? content; +} diff --git a/packages/dynamite/lib/src/models/response.g.dart b/packages/dynamite/lib/src/models/response.g.dart new file mode 100644 index 00000000..47ef4691 --- /dev/null +++ b/packages/dynamite/lib/src/models/response.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Response _$ResponseFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['description', 'content'], + ); + return Response( + description: json['description'] as String, + content: (json['content'] as Map?)?.map( + (k, e) => MapEntry(k, MediaType.fromJson(e as Map)), + ), + ); +} + +Map _$ResponseToJson(Response instance) { + final val = { + 'description': instance.description, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('content', instance.content?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/dynamite/lib/src/models/responses.dart b/packages/dynamite/lib/src/models/responses.dart new file mode 100644 index 00000000..f46537a4 --- /dev/null +++ b/packages/dynamite/lib/src/models/responses.dart @@ -0,0 +1,3 @@ +import 'package:dynamite/src/models/response.dart'; + +typedef Responses = Map; diff --git a/packages/dynamite/lib/src/models/schema.dart b/packages/dynamite/lib/src/models/schema.dart new file mode 100644 index 00000000..b3caf1fd --- /dev/null +++ b/packages/dynamite/lib/src/models/schema.dart @@ -0,0 +1,53 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'schema.g.dart'; + +@JsonSerializable() +class Schema { + Schema({ + required this.ref, + required this.oneOf, + required this.anyOf, + required this.allOf, + required this.description, + required this.deprecated, + required this.type, + required this.format, + required this.default_, + required this.enum_, + required this.properties, + required this.items, + }); + + factory Schema.fromJson(final Map json) => _$SchemaFromJson(json); + Map toJson() => _$SchemaToJson(this); + + @JsonKey(name: r'$ref') + final String? ref; + + final List? oneOf; + + final List? anyOf; + + final List? allOf; + + List? get ofs => oneOf ?? anyOf ?? allOf; + + final String? description; + + final bool? deprecated; + + final String? type; + + final String? format; + + @JsonKey(name: 'default') + final dynamic default_; + + @JsonKey(name: 'enum') + final List? enum_; + + final Map? properties; + + final Schema? items; +} diff --git a/packages/dynamite/lib/src/models/schema.g.dart b/packages/dynamite/lib/src/models/schema.g.dart new file mode 100644 index 00000000..9db4f07e --- /dev/null +++ b/packages/dynamite/lib/src/models/schema.g.dart @@ -0,0 +1,67 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'schema.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Schema _$SchemaFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const [ + r'$ref', + 'oneOf', + 'anyOf', + 'allOf', + 'description', + 'deprecated', + 'type', + 'format', + 'default', + 'enum', + 'properties', + 'items' + ], + ); + return Schema( + ref: json[r'$ref'] as String?, + oneOf: (json['oneOf'] as List?)?.map((e) => Schema.fromJson(e as Map)).toList(), + anyOf: (json['anyOf'] as List?)?.map((e) => Schema.fromJson(e as Map)).toList(), + allOf: (json['allOf'] as List?)?.map((e) => Schema.fromJson(e as Map)).toList(), + description: json['description'] as String?, + deprecated: json['deprecated'] as bool?, + type: json['type'] as String?, + format: json['format'] as String?, + default_: json['default'], + enum_: json['enum'] as List?, + properties: (json['properties'] as Map?)?.map( + (k, e) => MapEntry(k, Schema.fromJson(e as Map)), + ), + items: json['items'] == null ? null : Schema.fromJson(json['items'] as Map), + ); +} + +Map _$SchemaToJson(Schema instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull(r'$ref', instance.ref); + writeNotNull('oneOf', instance.oneOf?.map((e) => e.toJson()).toList()); + writeNotNull('anyOf', instance.anyOf?.map((e) => e.toJson()).toList()); + writeNotNull('allOf', instance.allOf?.map((e) => e.toJson()).toList()); + writeNotNull('description', instance.description); + writeNotNull('deprecated', instance.deprecated); + writeNotNull('type', instance.type); + writeNotNull('format', instance.format); + writeNotNull('default', instance.default_); + writeNotNull('enum', instance.enum_); + writeNotNull('properties', instance.properties?.map((k, e) => MapEntry(k, e.toJson()))); + writeNotNull('items', instance.items?.toJson()); + return val; +} diff --git a/packages/dynamite/lib/src/models/security_requirement.dart b/packages/dynamite/lib/src/models/security_requirement.dart new file mode 100644 index 00000000..bf0c97b1 --- /dev/null +++ b/packages/dynamite/lib/src/models/security_requirement.dart @@ -0,0 +1 @@ +typedef SecurityRequirement = Map>; diff --git a/packages/dynamite/lib/src/models/security_scheme.dart b/packages/dynamite/lib/src/models/security_scheme.dart new file mode 100644 index 00000000..0cc60176 --- /dev/null +++ b/packages/dynamite/lib/src/models/security_scheme.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'security_scheme.g.dart'; + +@JsonSerializable() +class SecurityScheme { + SecurityScheme({ + required this.type, + required this.description, + required this.scheme, + }); + + factory SecurityScheme.fromJson(final Map json) => _$SecuritySchemeFromJson(json); + Map toJson() => _$SecuritySchemeToJson(this); + + final String type; + final String? description; + final String? scheme; +} diff --git a/packages/dynamite/lib/src/models/security_scheme.g.dart b/packages/dynamite/lib/src/models/security_scheme.g.dart new file mode 100644 index 00000000..def488b5 --- /dev/null +++ b/packages/dynamite/lib/src/models/security_scheme.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'security_scheme.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SecurityScheme _$SecuritySchemeFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['type', 'description', 'scheme'], + ); + return SecurityScheme( + type: json['type'] as String, + description: json['description'] as String?, + scheme: json['scheme'] as String?, + ); +} + +Map _$SecuritySchemeToJson(SecurityScheme instance) { + final val = { + 'type': instance.type, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('description', instance.description); + writeNotNull('scheme', instance.scheme); + return val; +} diff --git a/packages/dynamite/lib/src/models/server.dart b/packages/dynamite/lib/src/models/server.dart new file mode 100644 index 00000000..5bd129db --- /dev/null +++ b/packages/dynamite/lib/src/models/server.dart @@ -0,0 +1,19 @@ +import 'package:dynamite/src/models/server_variable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'server.g.dart'; + +@JsonSerializable() +class Server { + Server({ + required this.url, + required this.variables, + }); + + factory Server.fromJson(final Map json) => _$ServerFromJson(json); + Map toJson() => _$ServerToJson(this); + + final String url; + + final Map? variables; +} diff --git a/packages/dynamite/lib/src/models/server.g.dart b/packages/dynamite/lib/src/models/server.g.dart new file mode 100644 index 00000000..b8e68b5d --- /dev/null +++ b/packages/dynamite/lib/src/models/server.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'server.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Server _$ServerFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['url', 'variables'], + ); + return Server( + url: json['url'] as String, + variables: (json['variables'] as Map?)?.map( + (k, e) => MapEntry(k, ServerVariable.fromJson(e as Map)), + ), + ); +} + +Map _$ServerToJson(Server instance) { + final val = { + 'url': instance.url, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('variables', instance.variables?.map((k, e) => MapEntry(k, e.toJson()))); + return val; +} diff --git a/packages/dynamite/lib/src/models/server_variable.dart b/packages/dynamite/lib/src/models/server_variable.dart new file mode 100644 index 00000000..f41240aa --- /dev/null +++ b/packages/dynamite/lib/src/models/server_variable.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'server_variable.g.dart'; + +@JsonSerializable() +class ServerVariable { + ServerVariable({ + required this.default_, + required this.enum_, + required this.description, + }); + + factory ServerVariable.fromJson(final Map json) => _$ServerVariableFromJson(json); + Map toJson() => _$ServerVariableToJson(this); + + @JsonKey(name: 'default') + final String default_; + + @JsonKey(name: 'enum') + final List? enum_; + + final String? description; +} diff --git a/packages/dynamite/lib/src/models/server_variable.g.dart b/packages/dynamite/lib/src/models/server_variable.g.dart new file mode 100644 index 00000000..699b16f9 --- /dev/null +++ b/packages/dynamite/lib/src/models/server_variable.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'server_variable.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ServerVariable _$ServerVariableFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['default', 'enum', 'description'], + ); + return ServerVariable( + default_: json['default'] as String, + enum_: (json['enum'] as List?)?.map((e) => e as String).toList(), + description: json['description'] as String?, + ); +} + +Map _$ServerVariableToJson(ServerVariable instance) { + final val = { + 'default': instance.default_, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('enum', instance.enum_); + writeNotNull('description', instance.description); + return val; +} diff --git a/packages/dynamite/lib/src/models/tag.dart b/packages/dynamite/lib/src/models/tag.dart new file mode 100644 index 00000000..dfc342b6 --- /dev/null +++ b/packages/dynamite/lib/src/models/tag.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'tag.g.dart'; + +@JsonSerializable() +class Tag { + Tag({ + required this.name, + }); + + factory Tag.fromJson(final Map json) => _$TagFromJson(json); + Map toJson() => _$TagToJson(this); + + final String name; +} diff --git a/packages/dynamite/lib/src/models/tag.g.dart b/packages/dynamite/lib/src/models/tag.g.dart new file mode 100644 index 00000000..5462a11d --- /dev/null +++ b/packages/dynamite/lib/src/models/tag.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tag.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Tag _$TagFromJson(Map json) { + $checkKeys( + json, + allowedKeys: const ['name'], + ); + return Tag( + name: json['name'] as String, + ); +} + +Map _$TagToJson(Tag instance) => { + 'name': instance.name, + }; diff --git a/packages/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/lib/src/openapi_builder.dart new file mode 100644 index 00000000..02a037d5 --- /dev/null +++ b/packages/dynamite/lib/src/openapi_builder.dart @@ -0,0 +1,1244 @@ +part of '../dynamite.dart'; + +class OpenAPIBuilder implements Builder { + @override + final buildExtensions = const { + '.openapi.json': ['.openapi.dart'], + }; + + @override + Future build(final BuildStep buildStep) async { + try { + final inputId = buildStep.inputId; + final outputId = inputId.changeExtension('.dart'); + + final emitter = DartEmitter( + orderDirectives: true, + useNullSafetySyntax: true, + ); + + final spec = OpenAPI.fromJson( + json.decode( + await buildStep.readAsString(inputId), + ) as Map, + ); + final tags = [ + null, + if (spec.tags != null) ...[ + ...spec.tags!, + ], + ]; + final hasAnySecurity = spec.security?.isNotEmpty ?? false; + + final resolvedTypes = []; + final registeredJsonObjects = []; + final output = [ + "import 'dart:convert';", + "import 'dart:typed_data';", + '', + "import 'package:http/http.dart' as http;", + "import 'package:json_annotation/json_annotation.dart';", + '', + "part '${p.basename(outputId.changeExtension('.g.dart').path)}';", + '', + Class( + (final b) => b + ..name = 'Response' + ..fields.addAll([ + Field( + (final b) => b + ..name = 'statusCode' + ..type = refer('int') + ..modifier = FieldModifier.final$, + ), + Field( + (final b) => b + ..name = 'headers' + ..type = refer('Map') + ..modifier = FieldModifier.final$, + ), + Field( + (final b) => b + ..name = 'body' + ..type = refer('Uint8List') + ..modifier = FieldModifier.final$, + ), + ]) + ..constructors.add( + Constructor( + (final b) => b + ..requiredParameters.addAll( + ['statusCode', 'headers', 'body'].map( + (final name) => Parameter( + (final b) => b + ..name = name + ..toThis = true, + ), + ), + ), + ), + ) + ..methods.add( + Method( + (final b) => b + ..name = 'toString' + ..returns = refer('String') + ..annotations.add(refer('override')) + ..lambda = true + ..body = + const Code(r"'Response(statusCode: $statusCode, headers: $headers, body: ${utf8.decode(body)})'"), + ), + ), + ).accept(emitter).toString(), + Class( + (final b) => b + ..name = 'ApiException' + ..extend = refer('Response') + ..implements.add(refer('Exception')) + ..constructors.addAll( + [ + Constructor( + (final b) => b + ..requiredParameters.addAll( + ['statusCode', 'headers', 'body'].map( + (final name) => Parameter( + (final b) => b + ..name = name + ..toSuper = true, + ), + ), + ), + ), + Constructor( + (final b) => b + ..name = 'fromResponse' + ..factory = true + ..lambda = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'response' + ..type = refer('Response'), + ), + ) + ..body = const Code('ApiException(response.statusCode, response.headers, response.body,)'), + ), + ], + ) + ..methods.add( + Method( + (final b) => b + ..name = 'toString' + ..returns = refer('String') + ..annotations.add(refer('override')) + ..lambda = true + ..body = const Code( + r"'ApiException(statusCode: ${super.statusCode}, headers: ${super.headers}, body: ${utf8.decode(super.body)})'", + ), + ), + ), + ).accept(emitter).toString(), + if (hasAnySecurity) ...[ + Class( + (final b) => b + ..name = 'Authentication' + ..abstract = true + ..methods.add( + Method( + (final b) => b + ..name = 'headers' + ..type = MethodType.getter + ..returns = refer('Map'), + ), + ), + ).accept(emitter).toString(), + ], + ]; + + if (spec.security != null) { + for (final securityRequirement in spec.security!) { + for (final name in securityRequirement.keys) { + final securityScheme = spec.components!.securitySchemes![name]!; + switch (securityScheme.type) { + case 'http': + switch (securityScheme.scheme) { + case 'basic': + output.add( + Class( + (final b) { + final fields = ['username', 'password']; + b + ..name = 'HttpBasicAuthentication' + ..extend = refer('Authentication') + ..constructors.add( + Constructor( + (final b) => b + ..optionalParameters.addAll( + fields.map( + (final name) => Parameter( + (final b) => b + ..name = name + ..toThis = true + ..named = true + ..required = true, + ), + ), + ), + ), + ) + ..fields.addAll( + fields.map( + (final name) => Field( + (final b) => b + ..name = name + ..type = refer('String') + ..modifier = FieldModifier.final$, + ), + ), + ) + ..methods.add( + Method( + (final b) => b + ..name = 'headers' + ..type = MethodType.getter + ..returns = refer('Map') + ..lambda = true + ..body = const Code(r''' + { + 'Authorization': 'Basic ${base64.encode(utf8.encode('$username:$password'))}', + } + '''), + ), + ); + }, + ).accept(emitter).toString(), + ); + continue; + } + } + throw Exception('Can not work with security scheme ${securityScheme.toJson()}'); + } + } + } + + TypeResolveResult resolveType( + final String identifier, + final Schema schema, { + final bool ignoreEnum = false, + }) { + TypeResolveResult? result; + if (schema.ref != null) { + final name = schema.ref!.split('/').last; + result = resolveType( + name, + spec.components!.schemas![name]!, + ); + } else if (schema.ofs != null) { + if (!resolvedTypes.contains(identifier)) { + resolvedTypes.add(identifier); + + final results = + schema.ofs!.map((final s) => resolveType('$identifier${schema.ofs!.indexOf(s)}', s)).toList(); + + output.add( + Class( + (final b) { + final fields = {}; + for (final result in results) { + final dartName = _toDartName(result.typeName); + final fieldName = dartName == result.typeName ? '${dartName}_' : dartName; + fields[result.typeName] = fieldName; + } + + b + ..name = identifier + ..fields.addAll([ + Field( + (final b) { + b + ..name = '_data' + ..type = refer('dynamic') + ..modifier = FieldModifier.final$; + }, + ), + for (final result in results) ...[ + Field( + (final b) { + final s = schema.ofs![results.indexOf(result)]; + b + ..name = fields[result.typeName]! + ..type = refer(_makeNullable(result.typeName, true)) + ..modifier = FieldModifier.final$ + ..docs.addAll([ + if (s.description != null && s.description!.isNotEmpty) ...[ + '/// ${s.description!}', + ], + ]); + }, + ), + ], + ]) + ..constructors.addAll([ + Constructor( + (final b) => b + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = '_data' + ..toThis = true, + ), + ) + ..optionalParameters.addAll([ + for (final result in results) ...[ + Parameter( + (final b) => b + ..name = fields[result.typeName]! + ..toThis = true + ..named = true, + ), + ], + ]), + ), + Constructor( + (final b) { + b + ..factory = true + ..name = 'fromJson' + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'data' + ..type = refer('dynamic'), + ), + ) + ..body = Code( + [ + for (final result in results) ...[ + '${result.typeName}? ${fields[result.typeName]!};', + 'try {', + '${fields[result.typeName]!} = ${_deserializeFunctionForType('data', result)};', + '} catch (_) {', + '}', + ], + if (schema.oneOf != null) ...[ + "assert([${fields.values.join(',')}].where((final x) => x != null).length == 1, 'Need oneOf');", + ], + if (schema.allOf != null) ...[ + "assert([${fields.values.join(',')}].where((final x) => x != null).length == ${fields.length}, 'Need allOf');", + ], + 'return $identifier(', + 'data,', + for (final result in results) ...[ + '${fields[result.typeName]!}: ${fields[result.typeName]!},', + ], + ');', + ].join(), + ); + }, + ), + ]) + ..methods.add( + Method( + (final b) => b + ..name = 'toJson' + ..returns = refer('dynamic') + ..lambda = true + ..body = const Code('_data'), + ), + ); + }, + ).accept(emitter).toString(), + ); + } + + result = TypeResolveResult( + identifier, + isBaseType: false, + ); + } else { + switch (schema.type) { + case 'boolean': + result = TypeResolveResult('bool'); + break; + case 'integer': + result = TypeResolveResult('int'); + break; + case 'number': + result = TypeResolveResult('num'); + break; + case 'string': + switch (schema.format) { + case 'binary': + result = TypeResolveResult('Uint8List'); + break; + case null: + result = TypeResolveResult( + 'String', + ); + break; + } + break; + case 'array': + if (schema.items != null) { + final subResult = resolveType( + identifier, + schema.items!, + ); + result = TypeResolveResult( + 'List<${subResult.typeName}>', + isBaseType: false, + isList: true, + subType: subResult, + ); + } else { + result = TypeResolveResult( + 'List', + isBaseType: false, + isList: true, + ); + } + break; + case 'object': + if (schema.properties == null) { + result = TypeResolveResult('dynamic'); + break; + } + + if (!resolvedTypes.contains(identifier)) { + resolvedTypes.add(identifier); + registeredJsonObjects.add(identifier); + output.add( + Class( + (final b) { + b + ..name = identifier + ..docs.addAll([ + if (schema.description != null && schema.description!.isNotEmpty) ...[ + '/// ${schema.description!}', + ], + ]) + ..annotations.add(refer('JsonSerializable').call([])) + ..constructors.addAll( + [ + Constructor( + (final b) => b + ..optionalParameters.addAll( + schema.properties!.keys.map( + (final propertyName) => Parameter( + (final b) => b + ..name = _toDartName(propertyName) + ..toThis = true + ..named = true, + ), + ), + ), + ), + Constructor( + (final b) => b + ..factory = true + ..name = 'fromJson' + ..lambda = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'json' + ..type = refer('Map'), + ), + ) + ..body = Code('_\$${identifier}FromJson(json)'), + ), + ], + ) + ..methods.add( + Method( + (final b) => b + ..name = 'toJson' + ..returns = refer('Map') + ..lambda = true + ..body = Code('_\$${identifier}ToJson(this)'), + ), + ) + ..fields.addAll([ + for (final propertyName in schema.properties!.keys) ...[ + Field( + (final b) { + final result = resolveType( + _toDartName( + '$identifier${_toDartName(propertyName, uppercaseFirstCharacter: true)}', + uppercaseFirstCharacter: true, + ), + schema.properties![propertyName]!, + ); + + final propertySchema = schema.properties![propertyName]!; + + b + ..name = _toDartName(propertyName) + ..type = refer(_makeNullable(result.typeName, true)) + ..modifier = FieldModifier.final$ + ..docs.addAll([ + if (propertySchema.description != null && + propertySchema.description!.isNotEmpty) ...[ + '/// ${propertySchema.description!}', + ], + ]); + if (_toDartName(propertyName) != propertyName) { + b.annotations.add( + refer('JsonKey').call( + [], + { + 'name': refer("'$propertyName'"), + }, + ), + ); + } + }, + ) + ], + ]); + }, + ).accept(emitter).toString(), + ); + } + result = TypeResolveResult( + identifier, + isBaseType: false, + ); + break; + } + } + + if (result != null) { + if (!ignoreEnum && schema.enum_ != null) { + if (!resolvedTypes.contains(identifier)) { + resolvedTypes.add(identifier); + + output.add( + Enum( + (final b) => b + ..name = identifier + ..constructors.add( + Constructor( + (final b) => b + ..constant = true + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'value' + ..toThis = true, + ), + ), + ), + ) + ..fields.add( + Field( + (final b) => b + ..name = 'value' + ..type = refer(result!.typeName) + ..modifier = FieldModifier.final$, + ), + ) + ..values.addAll( + schema.enum_!.map( + (final value) => EnumValue( + (final b) { + final result = resolveType( + '$identifier${_toDartName(value.toString(), uppercaseFirstCharacter: true)}', + schema, + ignoreEnum: true, + ); + b + ..name = _toDartName(value.toString()) + ..arguments.add( + refer(_valueToEscapedValue(result.typeName, value)), + ); + if (_toDartName(value.toString()) != value.toString()) { + if (result.typeName != 'String' && result.typeName != 'int') { + throw Exception( + 'Sorry enum values are a bit broken. ' + 'See https://github.com/google/json_serializable.dart/issues/616. ' + 'Please remove the enum values on $identifier.', + ); + } + b.annotations.add( + refer('JsonValue').call([ + refer(_valueToEscapedValue(result.typeName, value.toString())), + ]), + ); + } + }, + ), + ), + ) + ..methods.add( + Method( + (final b) => b + ..name = 'fromValue' + ..static = true + ..returns = refer(identifier) + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'value' + ..type = refer(result!.typeName), + ), + ) + ..body = Code( + [ + 'switch (value) {', + for (final value in schema.enum_!) ...[ + 'case ${_valueToEscapedValue(result!.typeName, value)}:', + 'return $identifier.${_toDartName(value.toString())};', + ], + 'default:', + 'throw Exception(\'Can not parse UserStatusClearAtTime0 from "\$value"\');', + '}', + ].join(), + ), + ), + ), + ).accept(emitter).toString(), + ); + } + result = TypeResolveResult( + identifier, + isBaseType: false, + isEnum: true, + subType: result, + ); + } + + return result; + } + + throw Exception('Can not convert OpenAPI type "${schema.toJson()}" to a Dart type'); + } + + for (final tag in tags) { + final isRootClient = tag == null; + final paths = {}; + + if (spec.paths != null) { + for (final path in spec.paths!.keys) { + final pathItem = spec.paths![path]!; + for (final method in pathItem.operations.keys) { + final operation = pathItem.operations[method]!; + if ((tag != null && operation.tags != null && operation.tags!.contains(tag.name)) || + (tag == null && (operation.tags == null || operation.tags!.isEmpty))) { + if (paths[path] == null) { + paths[path] = PathItem( + description: pathItem.description, + parameters: pathItem.parameters, + get: null, + put: null, + post: null, + delete: null, + options: null, + head: null, + patch: null, + trace: null, + ); + } + paths[path] = paths[path]!.copyWithOperations({method: operation}); + } + } + } + } + + if (paths.isEmpty && !isRootClient) { + continue; + } + + output.add( + Class( + (final b) { + if (isRootClient) { + b + ..fields.addAll([ + Field( + (final b) => b + ..name = 'baseURL' + ..type = refer('String') + ..modifier = FieldModifier.final$, + ), + Field( + (final b) => b + ..name = 'baseHeaders' + ..type = refer('Map') + ..modifier = FieldModifier.final$ + ..late = true, + ), + if (hasAnySecurity) ...[ + Field( + (final b) => b + ..name = 'authentication' + ..type = refer('Authentication?') + ..modifier = FieldModifier.final$, + ), + ], + ]) + ..constructors.add( + Constructor( + (final b) => b + ..requiredParameters.add( + Parameter( + (final b) => b + ..name = 'baseURL' + ..toThis = true, + ), + ) + ..optionalParameters.addAll([ + Parameter( + (final b) => b + ..name = 'baseHeaders' + ..type = refer('Map?') + ..named = true, + ), + if (hasAnySecurity) ...[ + Parameter( + (final b) => b + ..name = 'authentication' + ..toThis = true + ..named = true, + ), + ], + ]) + ..body = Code(''' + this.baseHeaders = { + if (baseHeaders != null) ...{ + ...baseHeaders, + }, + ${hasAnySecurity ? ''' + if (authentication != null) ...{ + ...authentication!.headers, + }, + ''' : ''} + }; + '''), + ), + ) + ..methods.addAll([ + if (isRootClient) ...[ + for (final tag in tags.where((final tag) => tag != null).toList().cast()) ...[ + Method( + (final b) => b + ..name = _toDartName(tag.name) + ..lambda = true + ..type = MethodType.getter + ..returns = refer(_clientName(tag)) + ..body = Code('${_clientName(tag)}(this)'), + ), + ], + ], + Method( + (final b) => b + ..name = '_doRequest' + ..returns = refer('Future') + ..modifier = MethodModifier.async + ..requiredParameters.addAll([ + Parameter( + (final b) => b + ..name = 'method' + ..type = refer('String'), + ), + Parameter( + (final b) => b + ..name = 'path' + ..type = refer('String'), + ), + Parameter( + (final b) => b + ..name = 'headers' + ..type = refer('Map'), + ), + Parameter( + (final b) => b + ..name = 'body' + ..type = refer('Uint8List?'), + ), + ]) + ..body = const Code(r''' + final request = http.Request(method, Uri.parse('$baseURL$path')); + request.headers.addAll(baseHeaders); + request.headers.addAll(headers); + if (body != null) { + request.bodyBytes = body.toList(); + } + + final response = await http.Response.fromStream(await request.send()); + return Response( + response.statusCode, + response.headers, + response.bodyBytes, + ); + '''), + ), + ]); + } else { + b + ..fields.add( + Field( + (final b) => b + ..name = '_client' + ..type = refer('Client') + ..modifier = FieldModifier.final$, + ), + ) + ..constructors.add( + Constructor( + (final b) => b.requiredParameters.add( + Parameter( + (final b) => b + ..name = '_client' + ..toThis = true, + ), + ), + ), + ); + } + b + ..name = isRootClient ? 'Client' : _clientName(tag) + ..methods.addAll( + [ + for (final path in paths.keys) ...[ + for (final httpMethod in paths[path]!.operations.keys) ...[ + Method( + (final b) { + final operation = paths[path]!.operations[httpMethod]!; + final pathParameters = [ + if (paths[path]!.parameters != null) ...paths[path]!.parameters!, + ]; + final parameters = [ + ...pathParameters, + if (operation.parameters != null) ...operation.parameters!, + ]; + final methodName = _toDartName(operation.operationId ?? _toDartName('$httpMethod-$path')); + b + ..name = methodName + ..modifier = MethodModifier.async; + + final code = StringBuffer(''' + var path = '$path'; + final queryParameters = {}; + final headers = {}; + Uint8List? body; + '''); + + for (final parameter in parameters) { + final nullable = _isParameterNullable( + parameter.required, + parameter.schema?.default_, + ); + + final result = resolveType( + _toDartName( + parameter.name, + uppercaseFirstCharacter: true, + ), + parameter.schema!, + ); + + b.optionalParameters.add( + Parameter( + (final b) { + b + ..named = true + ..name = _toDartName(parameter.name) + ..required = parameter.required ?? false; + if (parameter.schema != null) { + if (parameter.schema!.default_ != null) { + final value = parameter.schema!.default_!.toString(); + final result = resolveType( + parameter.schema!.type!, + parameter.schema!, + ); + b.defaultTo = Code(_valueToEscapedValue(result.typeName, value)); + } + + b.type = refer( + _makeNullable( + result.typeName, + nullable, + ), + ); + } + }, + ), + ); + + if (nullable) { + code.write('if (${_toDartName(parameter.name)} != null) {'); + } + final enumValueGetter = result.isEnum ? '.value' : ''; + switch (parameter.in_) { + case 'path': + code.write( + "path = path.replaceAll('{${parameter.name}}', Uri.encodeQueryComponent(${_toDartName(parameter.name)}$enumValueGetter.toString()));", + ); + break; + case 'query': + code.write( + "queryParameters['${parameter.name}'] = ${_toDartName(parameter.name)}$enumValueGetter.toString();", + ); + break; + case 'header': + code.write( + "headers['${parameter.name}'] = ${_toDartName(parameter.name)}$enumValueGetter.toString();", + ); + break; + default: + throw Exception('Can not work with parameter in "${parameter.in_}"'); + } + if (nullable) { + code.write('}'); + } + } + + if (operation.requestBody != null) { + if (operation.requestBody!.content!.length > 1) { + throw Exception('Can not work with multiple mime types right now'); + } + for (final mimeType in operation.requestBody!.content!.keys) { + final mediaType = operation.requestBody!.content![mimeType]!; + + code.write("headers['Content-Type'] = '$mimeType';"); + + final result = resolveType( + _toDartName(methodName, uppercaseFirstCharacter: true), + mediaType.schema!, + ); + switch (mimeType) { + case 'application/json': + b.optionalParameters.add( + Parameter( + (final b) => b + ..name = _toDartName(result.typeName) + ..type = refer(result.typeName) + ..named = true + ..required = operation.requestBody!.required ?? false, + ), + ); + final nullable = _isParameterNullable( + operation.requestBody!.required, + mediaType.schema?.default_, + ); + if (nullable) { + code.write('if (${_toDartName(result.typeName)} != null) {'); + } + code.write( + 'body = Uint8List.fromList(utf8.encode(${result.isBaseType ? '' : 'json.encode('}${_serializeFunctionForType(_toDartName(result.typeName), result)}${result.isBaseType ? '' : ')'}));', + ); + if (nullable) { + code.write('}'); + } + break; + default: + throw Exception('Can not parse mime type "$mimeType"'); + } + } + } + + code.write( + ''' + final response = await ${isRootClient ? '' : '_client.'}_doRequest( + '$httpMethod', + Uri(path: path, queryParameters: queryParameters).toString(), + headers, + body, + ); + ''', + ); + + if (operation.responses != null) { + if (operation.responses!.length > 1) { + throw Exception('Can not work with multiple status codes right now'); + } + for (final statusCode in operation.responses!.keys) { + final response = operation.responses![statusCode]!; + code.write('if (response.statusCode == $statusCode) {'); + if (response.content != null) { + if (response.content!.length > 1) { + throw Exception('Can not work with multiple mime types right now'); + } + for (final mimeType in response.content!.keys) { + final mediaType = response.content![mimeType]!; + + final result = resolveType( + _toDartName(methodName, uppercaseFirstCharacter: true), + mediaType.schema!, + ); + switch (mimeType) { + case 'application/json': + b.returns = refer('Future<${result.typeName}>'); + code.write('return ${_deserializeFunctionForType( + result.isBaseType + ? 'utf8.decode(response.body)' + : 'json.decode(utf8.decode(response.body))', + result, + )};'); + break; + case 'image/png': + b.returns = refer('Future'); + code.write('return response.body;'); + break; + default: + throw Exception('Can not parse mime type "$mimeType"'); + } + } + } else { + code.write('return;'); + b.returns = refer('Future'); + } + code.write('}'); + } + code.write('throw ApiException.fromResponse(response);'); + } else { + b.returns = refer('Future'); + } + b.body = Code(code.toString()); + }, + ), + ], + ], + ], + ); + }, + ).accept(emitter).toString(), + ); + } + + if (spec.components?.schemas != null) { + for (final name in spec.components!.schemas!.keys) { + final schema = spec.components!.schemas![name]!; + + final identifier = _toDartName(name, uppercaseFirstCharacter: true); + if (schema.type == null && schema.ref == null && schema.ofs == null) { + output.add('typedef $identifier = dynamic;'); + } else { + final result = resolveType( + identifier, + schema, + ); + if (result.isBaseType) { + output.add('typedef $identifier = ${result.typeName};'); + } + } + } + } + + if (registeredJsonObjects.isNotEmpty) { + output.addAll([ + 'final _deserializers = {', + for (final name in registeredJsonObjects) ...[ + '$name: (final data) => ${_deserializeFunctionForType( + 'data', + TypeResolveResult( + name, + isBaseType: false, + ), + )},', + 'List<$name>: (final data) => ${_deserializeFunctionForType( + 'data', + TypeResolveResult( + 'List<$name>', + isList: true, + subType: TypeResolveResult( + name, + isBaseType: false, + ), + ), + )},', + ], + '};', + '', + 'final _serializers = {', + for (final name in registeredJsonObjects) ...[ + '$name: (final data) => ${_serializeFunctionForType( + 'data', + TypeResolveResult( + name, + isBaseType: false, + ), + )},', + 'List<$name>: (final data) => ${_serializeFunctionForType( + 'data', + TypeResolveResult( + 'List<$name>', + isList: true, + subType: TypeResolveResult( + name, + isBaseType: false, + ), + ), + )},', + ], + '};', + '', + 'T deserialize(final dynamic data) => _deserializers[T]!(data) as T;', + '', + 'dynamic serialize(final T data) => _serializers[T]!(data);', + ]); + } + + final formatter = DartFormatter( + pageWidth: 120, + ); + await buildStep.writeAsString( + outputId, + formatter.format(output.join('\n')), + ); + } catch (e, s) { + print(s); + + rethrow; + } + } +} + +String _clientName(final Tag tag) => '${_toDartName(tag.name, uppercaseFirstCharacter: true)}Client'; + +String _toDartName( + final String input, { + final bool uppercaseFirstCharacter = false, +}) { + final result = StringBuffer(); + + final parts = input.split(''); + for (var i = 0; i < parts.length; i++) { + var char = parts[i]; + final prevChar = i > 0 ? parts[i - 1] : null; + if (_isNonAlphaNumericString(char)) { + continue; + } + if (prevChar != null && _isNonAlphaNumericString(prevChar)) { + char = char.toUpperCase(); + } + if (i == 0) { + if (uppercaseFirstCharacter) { + char = char.toUpperCase(); + } else { + char = char.toLowerCase(); + } + } + result.write(char); + } + + if (_dartKeywords.contains(result.toString())) { + return '${result.toString()}_'; + } + + return result.toString(); +} + +final _dartKeywords = [ + 'assert', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'default', + 'do', + 'else', + 'enum', + 'extends', + 'false', + 'final', + 'finally', + 'for', + 'if', + 'in', + 'is', + 'new', + 'null', + 'rethrow', + 'return', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'var', + 'void', + 'while', + 'with', + 'async', + 'hide', + 'on', + 'show', + 'sync', + 'abstract', + 'as', + 'covariant', + 'deferred', + 'dynamic', + 'export', + 'extension', + 'external', + 'factory', + 'function', + 'get', + 'implements', + 'import', + 'interface', + 'library', + 'mixin', + 'operator', + 'part', + 'set', + 'static', + 'typedef', +]; + +bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); + +String _makeNullable(final String type, final bool nullable) => nullable ? '$type?' : type; + +class TypeResolveResult { + TypeResolveResult( + this.typeName, { + this.isBaseType = true, + this.isList = false, + this.isEnum = false, + this.subType, + }); + + final String typeName; + final bool isBaseType; + final bool isList; + final bool isEnum; + final TypeResolveResult? subType; +} + +String _serializeFunctionForType(final String object, final TypeResolveResult result) { + if (result.isList) { + return '($object as ${result.typeName}).map((final e) => ${_serializeFunctionForType('e', result.subType!)}).toList()'; + } else if (result.isBaseType) { + return '$object.toString()'; + } else if (result.isEnum) { + return '($object as ${result.typeName}).value'; + } else { + return '($object as ${result.typeName}).toJson()'; + } +} + +String _deserializeFunctionForType(final String object, final TypeResolveResult result) { + if (result.isList) { + if (result.subType == null) { + return '$object as List'; + } + return '($object as List).map<${result.subType!.typeName}>((final e) => ${_deserializeFunctionForType('e', result.subType!)}).toList()'; + } else if (result.isBaseType) { + return '$object as ${result.typeName}'; + } else if (result.isEnum) { + return '${result.typeName}.fromValue($object as ${result.subType!.typeName})'; + } else { + return '${result.typeName}.fromJson($object as Map)'; + } +} + +bool _isParameterNullable(final bool? required, final dynamic default_) => !(required ?? false) && default_ == null; + +String _valueToEscapedValue(final String type, final dynamic value) => type == 'String' ? "'$value'" : value.toString(); diff --git a/packages/dynamite/mono_pkg.yaml b/packages/dynamite/mono_pkg.yaml new file mode 100644 index 00000000..092827e8 --- /dev/null +++ b/packages/dynamite/mono_pkg.yaml @@ -0,0 +1,7 @@ +sdk: + - stable + +stages: + - analyze: + - analyze: --fatal-infos . + - format: --output=none --set-exit-if-changed --line-length 120 . diff --git a/packages/dynamite/pubspec.yaml b/packages/dynamite/pubspec.yaml new file mode 100644 index 00000000..64efb1ce --- /dev/null +++ b/packages/dynamite/pubspec.yaml @@ -0,0 +1,20 @@ +name: dynamite +version: 1.0.0 + +environment: + sdk: '>=2.18.0 <3.0.0' + +dependencies: + build: ^2.3.1 + code_builder: ^4.3.0 + dart_style: ^2.2.4 + json_annotation: ^4.6.0 + path: ^1.8.2 + +dev_dependencies: + build_runner: ^2.2.1 + json_serializable: ^6.3.2 + nit_picking: + git: + url: https://github.com/stack11/dart_nit_picking + ref: f29382f diff --git a/tool/.gitattributes b/tool/.gitattributes new file mode 100644 index 00000000..73533a73 --- /dev/null +++ b/tool/.gitattributes @@ -0,0 +1 @@ +ci.sh -diff diff --git a/tool/ci.sh b/tool/ci.sh index a9090e11..e3477c62 100755 --- a/tool/ci.sh +++ b/tool/ci.sh @@ -68,13 +68,13 @@ for PKG in ${PKGS}; do echo -e "\033[1mPKG: ${PKG}; TASK: ${TASK}\033[22m" case ${TASK} in analyze_0) - echo 'flutter analyze --fatal-infos .' - flutter analyze --fatal-infos . || EXIT_CODE=$? - ;; - analyze_1) echo 'dart analyze --fatal-infos .' dart analyze --fatal-infos . || EXIT_CODE=$? ;; + analyze_1) + echo 'flutter analyze --fatal-infos .' + flutter analyze --fatal-infos . || EXIT_CODE=$? + ;; format) echo 'dart format --output=none --set-exit-if-changed --line-length 120 .' dart format --output=none --set-exit-if-changed --line-length 120 . || EXIT_CODE=$?