diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 1c74b34c55..cd67a62ed2 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -127,9 +127,8 @@ jobs: with: packages: '' - - name: Get Packages - working-directory: ./mobile - run: flutter pub get + - name: Install dependencies + run: mise //mobile:install:ci - name: Generate translation file run: mise //mobile:codegen:translation @@ -226,9 +225,8 @@ jobs: github_token: ${{ steps.token.outputs.token }} working_directory: ./mobile - - name: Install Flutter dependencies - working-directory: ./mobile - run: flutter pub get + - name: Install dependencies + run: mise //mobile:install:ci - name: Generate translation files run: mise //mobile:codegen:translation diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 7df2bfd6ab..08ec030541 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -48,9 +48,8 @@ jobs: github_token: ${{ github.token }} working_directory: ./mobile - - name: Get packages - working-directory: ./mobile - run: flutter pub get + - name: Install dependencies + run: mise //mobile:install:ci - name: Fetch base spec from main run: | diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index adfdde6821..3891ba1511 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -70,10 +70,10 @@ jobs: working_directory: ./mobile - name: Install dependencies - run: flutter pub get + run: mise //mobile:install:ci - name: Install dependencies for UI package - run: flutter pub get + run: flutter pub get --enforce-lockfile working-directory: ./mobile/packages/ui - name: Generate translation files diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0589fbccec..d2d6a7db00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -610,8 +610,7 @@ jobs: working_directory: ./mobile - name: Install dependencies - run: flutter pub get - working-directory: ./mobile + run: mise //mobile:install:ci - name: Generate translation files run: mise //mobile:codegen:translation @@ -920,6 +919,6 @@ jobs: # emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # disable-animations: true # script: | -# flutter pub get +# flutter pub get --enforce-lockfile # flutter test integration_test # attempt_limit: 3 diff --git a/mise.lock b/mise.lock index a400a1e5c7..656d11b3eb 100644 --- a/mise.lock +++ b/mise.lock @@ -243,6 +243,10 @@ url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" +[[tools."npm:@openapitools/openapi-generator-cli"]] +version = "2.40.1" +backend = "npm:@openapitools/openapi-generator-cli" + [[tools."npm:oazapfts"]] version = "7.5.0" backend = "npm:oazapfts" diff --git a/mise.toml b/mise.toml index 0b11993aa9..5ff9db5819 100644 --- a/mise.toml +++ b/mise.toml @@ -19,6 +19,7 @@ node = "24.15.0" pnpm = "11.13.1" terragrunt = "1.1.1" opentofu = "1.12.4" +"npm:@openapitools/openapi-generator-cli" = "2.40.1" "npm:oazapfts" = "7.5.0" "github:extism/cli" = "1.6.3" "github:webassembly/binaryen" = "version_124" @@ -61,6 +62,14 @@ run = [ [tasks.open-api-dart] dir = "open-api" +sources = [ + "immich-openapi-specs.json", + "openapitools.json", + "templates/**/*", + "patch/*", + "bin/generate-dart-sdk.sh", +] +outputs = ["../mobile/openapi/lib/"] run = "bash ./bin/generate-dart-sdk.sh" [tasks.open-api] diff --git a/mobile/mise.lock b/mobile/mise.lock index fda7ec56b0..8f2f9572fb 100644 --- a/mobile/mise.lock +++ b/mobile/mise.lock @@ -30,45 +30,6 @@ url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/wind version = "1.37.0" backend = "github:CQLabs/homebrew-dcm" -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64"] -checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"] -checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"] -checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"] -checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" - -[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] -checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" - -[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"] -checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727" - -[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"] -checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - [tools."github:CQLabs/homebrew-dcm".options] asset_pattern = "dcm-linux-arm-release.zip" @@ -103,6 +64,18 @@ url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/4045 version = "1.37.0" backend = "github:CQLabs/homebrew-dcm" +[tools."github:CQLabs/homebrew-dcm".options] +asset_pattern = "dcm-macos-arm-release.zip" + +[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] +checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" +url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" +url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" + +[[tools."github:CQLabs/homebrew-dcm"]] +version = "1.37.0" +backend = "github:CQLabs/homebrew-dcm" + [tools."github:CQLabs/homebrew-dcm".options] asset_pattern = "dcm-macos-x64-release.zip" @@ -123,42 +96,6 @@ checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e3158 url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip" url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660" -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-macos-arm-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] -checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" - -[[tools.java]] -version = "21.0.2" -backend = "core:java" - -[tools.java."platforms.linux-arm64"] -checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" - -[tools.java."platforms.linux-x64"] -checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" - -[tools.java."platforms.macos-arm64"] -checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" - -[tools.java."platforms.macos-x64"] -checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" - -[tools.java."platforms.windows-x64"] -checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" - [[tools.java]] version = "21.0.2" backend = "core:java" @@ -185,3 +122,7 @@ url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c55847602 [tools.java."platforms.windows-x64"] checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" + +[[tools."npm:@openapitools/openapi-generator-cli"]] +version = "2.40.1" +backend = "npm:@openapitools/openapi-generator-cli" diff --git a/mobile/mise.toml b/mobile/mise.toml index c5f06af006..869a52a048 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -91,8 +91,15 @@ run = "dart run drift_dev make-migrations" [tasks.install] alias = "install" description = "Install flutter dependencies" +depends = ["//:open-api-dart"] run = "flutter pub get" +[tasks."install:ci"] +description = "Install flutter dependencies in CI" +run = [ + { task = "install", args = ["--enforce-lockfile"] }, +] + [tasks.start] alias = "start" description = "Start flutter app" diff --git a/open-api/bin/generate-dart-sdk.sh b/open-api/bin/generate-dart-sdk.sh index 75b4d395ca..dd86970d18 100755 --- a/open-api/bin/generate-dart-sdk.sh +++ b/open-api/bin/generate-dart-sdk.sh @@ -1,22 +1,19 @@ #!/usr/bin/env bash -OPENAPI_GENERATOR_VERSION=v7.24.0 - set -euo pipefail # usage: ./bin/generate-dart-sdk.sh +TEMPLATE_DIR=$(mktemp -d) +trap 'rm -rf "$TEMPLATE_DIR"' EXIT + +# Installed via mise +openapi-generator-cli author template -g dart -o "$TEMPLATE_DIR" +patch --no-backup-if-mismatch -u "$TEMPLATE_DIR/api.mustache" <./templates/mobile/api.mustache.patch +patch --no-backup-if-mismatch -u "$TEMPLATE_DIR/serialization/native/native_class.mustache" <./templates/mobile/serialization/native/native_class.mustache.patch + rm -rf ../mobile/openapi -cd ./templates/mobile/serialization/native -wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache -patch --no-backup-if-mismatch -u native_class.mustache header}} -{{>part_of}} - -{{#operations}} - -class {{{classname}}} { - {{{classname}}}([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; - - final ApiClient apiClient; - {{#operation}} - - {{#summary}} - /// {{{.}}} - {{/summary}} - {{#notes}} - {{#summary}} - /// - {{/summary}} - /// {{{notes}}} - /// - /// Note: This method returns the HTTP [Response]. - {{/notes}} - {{^notes}} - {{#summary}} - /// - /// Note: This method returns the HTTP [Response]. - {{/summary}} - {{^summary}} - /// Performs an HTTP '{{{httpMethod}}} {{{path}}}' operation and returns the [Response]. - {{/summary}} - {{/notes}} - {{#hasParams}} - {{#summary}} - /// - {{/summary}} - {{^summary}} - {{#notes}} - /// - {{/notes}} - {{/summary}} - /// Parameters: - /// - {{/hasParams}} - {{#allParams}} - /// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}: - {{#description}} - /// {{{.}}} - {{/description}} - {{^-last}} - /// - {{/-last}} - {{/allParams}} - Future {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { - // ignore: prefer_const_declarations - final apiPath = r'{{{path}}}'{{#pathParams}} - .replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}}; - - // ignore: prefer_final_locals - Object? postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}}; - - final queryParams = []; - final headerParams = {}; - final formParams = {}; - {{#hasQueryParams}} - - {{#queryParams}} - {{^required}} - if ({{{paramName}}} != null) { - {{/required}} - queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}})); - {{^required}} - } - {{/required}} - {{/queryParams}} - {{/hasQueryParams}} - {{#hasHeaderParams}} - - {{#headerParams}} - {{#required}} - headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); - {{/required}} - {{^required}} - if ({{{paramName}}} != null) { - headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); - } - {{/required}} - {{/headerParams}} - {{/hasHeaderParams}} - - const contentTypes = [{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}]; - - {{#isMultipart}} - bool hasFields = false; - final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(apiPath)); - {{#formParams}} - {{^isFile}} - if ({{{paramName}}} != null) { - hasFields = true; - mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); - } - {{/isFile}} - {{#isFile}} - if ({{{paramName}}} != null) { - hasFields = true; - mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field; - mp.files.add({{{paramName}}}); - } - {{/isFile}} - {{/formParams}} - if (hasFields) { - postBody = mp; - } - {{/isMultipart}} - {{^isMultipart}} - {{#formParams}} - {{^isFile}} - if ({{{paramName}}} != null) { - formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}}); - } - {{/isFile}} - {{/formParams}} - {{/isMultipart}} - - return apiClient.invokeAPI( - apiPath, - '{{{httpMethod}}}', - queryParams, - postBody, - headerParams, - formParams, - contentTypes.isEmpty ? null : contentTypes.first, - abortTrigger: abortTrigger, - ); - } - - {{#summary}} - /// {{{.}}} - {{/summary}} - {{#notes}} - {{#summary}} - /// - {{/summary}} - /// {{{notes}}} - {{/notes}} - {{#hasParams}} - {{#summary}} - /// - {{/summary}} - {{^summary}} - {{#notes}} - /// - {{/notes}} - {{/summary}} - /// Parameters: - /// - {{/hasParams}} - {{#allParams}} - /// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}: - {{#description}} - /// {{{.}}} - {{/description}} - {{^-last}} - /// - {{/-last}} - {{/allParams}} - Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}, {{/required}}{{/allParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}}, {{/required}}{{/allParams}}Future? abortTrigger, }) async { - final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}}, {{/required}}{{/allParams}}{{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}}, {{/required}}{{/allParams}}abortTrigger: abortTrigger,); - if (response.statusCode >= HttpStatus.badRequest) { - throw ApiException(response.statusCode, await _decodeBodyBytes(response)); - } - {{#returnType}} - // When a remote server returns no body with a status of 204, we shall not decode it. - // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" - // FormatException when trying to decode an empty string. - if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { - {{#native_serialization}} - {{#isArray}} - final responseBody = await _decodeBodyBytes(response); - return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List) - .cast<{{{returnBaseType}}}>() - .{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}}; - {{/isArray}} - {{^isArray}} - {{#isMap}} - return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}'),); - {{/isMap}} - {{^isMap}} - return await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}',) as {{{returnType}}}; - {{/isMap}}{{/isArray}}{{/native_serialization}} - } - return null; - {{/returnType}} - } - {{/operation}} -} -{{/operations}} diff --git a/open-api/templates/mobile/serialization/native/native_class.mustache b/open-api/templates/mobile/serialization/native/native_class.mustache deleted file mode 100644 index 4d0e4d5cf8..0000000000 --- a/open-api/templates/mobile/serialization/native/native_class.mustache +++ /dev/null @@ -1,458 +0,0 @@ -class {{{classname}}} { -{{>dart_constructor}} - -{{#vars}} - {{#description}} - /// {{{.}}} - {{/description}} - {{^isEnum}} - {{#minimum}} - {{#description}} - /// - {{/description}} - /// Minimum value: {{{.}}} - {{/minimum}} - {{#maximum}} - {{#description}} - {{^minimum}} - /// - {{/minimum}} - {{/description}} - /// Maximum value: {{{.}}} - {{/maximum}} - {{^isNullable}} - {{^required}} - {{^defaultValue}} - /// - /// Please note: This property should have been non-nullable! Since the specification file - /// does not include a default value (using the "default:" property), however, the generated - /// source code must fall back to having a nullable type. - /// Consider adding a "default:" property in the specification file to hide this note. - /// - {{/defaultValue}} - {{/required}} - {{/isNullable}} - {{/isEnum}} - {{#required}} - {{#useFinalProperties}}final {{/useFinalProperties}}{{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}} {{{name}}}; - {{/required}} - {{^required}} - {{#vendorExtensions.x-is-optional}} - {{#useFinalProperties}}final {{/useFinalProperties}}{{{datatypeWithEnum}}} {{{name}}}; - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{#useFinalProperties}}final {{/useFinalProperties}}{{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^defaultValue}}?{{/defaultValue}}{{/isNullable}} {{{name}}}; - {{/vendorExtensions.x-is-optional}} - {{/required}} - -{{/vars}} - @override - bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} && - {{#vars}} - {{#isMap}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isMap}}{{^isMap}}{{#isArray}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isArray}}{{^isArray}}other.{{{name}}} == {{{name}}}{{/isArray}}{{/isMap}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}} - {{/vars}} - - @override - int get hashCode => - // ignore: unnecessary_parenthesis - {{#vars}} - ({{#isNullable}}{{{name}}} == null ? 0 : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}{{{name}}} == null ? 0 : {{/defaultValue}}{{/required}}{{/isNullable}}{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.hashCode){{^-last}} +{{/-last}}{{#-last}};{{/-last}} - {{/vars}} - - @override - String toString() => '{{{classname}}}[{{#vars}}{{{name}}}=${{{name}}}{{^-last}}, {{/-last}}{{/vars}}]'; - - Map toJson() { - final json = {}; - {{#vars}} - {{#vendorExtensions.x-is-optional}} - if (this.{{{name}}}.isPresent) { - final value = this.{{{name}}}.value; - {{#isDateTime}} - {{#pattern}} - json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') - ? value.millisecondsSinceEpoch - : value.toUtc().toIso8601String()); - {{/pattern}} - {{^pattern}} - json[r'{{{baseName}}}'] = value == null ? null : value.toUtc().toIso8601String(); - {{/pattern}} - {{/isDateTime}} - {{#isDate}} - {{#pattern}} - json[r'{{{baseName}}}'] = value == null ? null : (_isEpochMarker(r'{{{pattern}}}') - ? value.millisecondsSinceEpoch - : _dateFormatter.format(value.toUtc())); - {{/pattern}} - {{^pattern}} - json[r'{{{baseName}}}'] = value == null ? null : _dateFormatter.format(value.toUtc()); - {{/pattern}} - {{/isDate}} - {{^isDateTime}} - {{^isDate}} - json[r'{{{baseName}}}'] = value{{#isArray}}{{#uniqueItems}} == null ? null : value.toList(growable: false){{/uniqueItems}}{{/isArray}}; - {{/isDate}} - {{/isDateTime}} - } - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{#isNullable}} - if (this.{{{name}}} != null) { - {{/isNullable}} - {{^isNullable}} - {{^required}} - {{^defaultValue}} - if (this.{{{name}}} != null) { - {{/defaultValue}} - {{/required}} - {{/isNullable}} - {{#isDateTime}} - {{#pattern}} - json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}') - ? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch - : this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String(); - {{/pattern}} - {{^pattern}} - json[r'{{{baseName}}}'] = this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String(); - {{/pattern}} - {{/isDateTime}} - {{#isDate}} - {{#pattern}} - json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}') - ? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch - : _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc()); - {{/pattern}} - {{^pattern}} - json[r'{{{baseName}}}'] = _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc()); - {{/pattern}} - {{/isDate}} - {{^isDateTime}} - {{^isDate}} - json[r'{{{baseName}}}'] = this.{{{name}}}{{#isArray}}{{#uniqueItems}}{{#isNullable}}!{{/isNullable}}.toList(growable: false){{/uniqueItems}}{{/isArray}}; - {{/isDate}} - {{/isDateTime}} - {{#isNullable}} - } else { - json[r'{{{baseName}}}'] = null; - } - {{/isNullable}} - {{^isNullable}} - {{^required}} - {{^defaultValue}} - } else { - json[r'{{{baseName}}}'] = null; - } - {{/defaultValue}} - {{/required}} - {{/isNullable}} - {{/vendorExtensions.x-is-optional}} - {{/vars}} - return json; - } - -{{#useFinalProperties}} - /// Clones this instance of [{{{classname}}}] and returns a new one where some of the - /// properties have changed. - {{{classname}}} copyWith({ - {{#vars}} - {{{datatypeWithEnum}}}? {{{name}}},{{#isNullable}} - bool {{{name}}}SetToNull = false,{{/isNullable}} - {{/vars}} - }) => {{{classname}}}( - {{#vars}} - {{{name}}}: {{#isNullable}}{{{name}}}SetToNull ? null : {{/isNullable}}{{{name}}} ?? this.{{{name}}}, - {{/vars}} - ); - -{{/useFinalProperties}} - /// Returns a new [{{{classname}}}] instance and imports its values from - /// [value] if it's a [Map], null otherwise. - // ignore: prefer_constructors_over_static_methods - static {{{classname}}}? fromJson(dynamic value) { - upgradeDto(value, "{{{classname}}}"); - if (value is Map) { - final json = value.cast(); - - return {{{classname}}}( - {{#vars}} - {{#isDateTime}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isDateTime}} - {{#isDate}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isDate}} - {{^isDateTime}} - {{^isDate}} - {{#complexType}} - {{#isArray}} - {{#items.isArray}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is List - ? (json[r'{{{baseName}}}'] as List).map((e) => - {{#items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} - {{/items.complexType}} - {{^items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) - {{/items.complexType}} - ).toList() - : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: json[r'{{{baseName}}}'] is List - ? (json[r'{{{baseName}}}'] as List).map((e) => - {{#items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.complexType}}>[]{{/items.isNullable}} : {{items.complexType}}.listFromJson(e){{#uniqueItems}}.toSet(){{/uniqueItems}} - {{/items.complexType}} - {{^items.complexType}} - e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (e as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false) - {{/items.complexType}} - ).toList() - : {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}}, - {{/vendorExtensions.x-is-optional}} - {{/items.isArray}} - {{^items.isArray}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, - {{/vendorExtensions.x-is-optional}} - {{/items.isArray}} - {{/isArray}} - {{^isArray}} - {{#isMap}} - {{#items.isArray}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null - {{#items.complexType}} - : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), - {{/items.complexType}} - {{^items.complexType}} - : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false)))) : const Optional.absent(), - {{/items.complexType}} - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: json[r'{{{baseName}}}'] == null - ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - {{#items.complexType}} - : {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']), - {{/items.complexType}} - {{^items.complexType}} - : (json[r'{{{baseName}}}'] as Map).map((k, v) => MapEntry(k, v == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}>[]{{/items.isNullable}} : (v as List).map((value) => value as {{items.items.dataType}}{{#items.items.isNullable}}?{{/items.items.isNullable}}).toList(growable: false))), - {{/items.complexType}} - {{/vendorExtensions.x-is-optional}} - {{/items.isArray}} - {{^items.isArray}} - {{#items.isMap}} - {{#items.complexType}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{items.complexType}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']), - {{/vendorExtensions.x-is-optional}} - {{/items.complexType}} - {{^items.complexType}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/items.complexType}} - {{/items.isMap}} - {{^items.isMap}} - {{#items.complexType}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']), - {{/vendorExtensions.x-is-optional}} - {{/items.complexType}} - {{^items.complexType}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/items.complexType}} - {{/items.isMap}} - {{/items.isArray}} - {{/isMap}} - {{^isMap}} - {{#isBinary}} - {{{name}}}: null, // No support for decoding binary content from JSON - {{/isBinary}} - {{^isBinary}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{complexType}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isBinary}} - {{/isMap}} - {{/isArray}} - {{/complexType}} - {{^complexType}} - {{#isArray}} - {{#isEnum}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}, - {{/vendorExtensions.x-is-optional}} - {{/isEnum}} - {{^isEnum}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] is Iterable - ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} - : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: json[r'{{{baseName}}}'] is Iterable - ? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}} - : {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}, - {{/vendorExtensions.x-is-optional}} - {{/isEnum}} - {{/isArray}} - {{^isArray}} - {{#isMap}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapCastOfType(json, r'{{{baseName}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapCastOfType(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isMap}} - {{^isMap}} - {{#isNumber}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null - ? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}} - : {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'), - {{/vendorExtensions.x-is-optional}} - {{/isNumber}} - {{^isNumber}} - {{#vendorExtensions.x-original-is-integer}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : int.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), - {{/vendorExtensions.x-original-is-integer}} - {{^vendorExtensions.x-original-is-integer}} - {{#vendorExtensions.x-original-is-number}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(json[r'{{{baseName}}}'] == null ? null : num.parse('${json[r'{{{baseName}}}']}')) : const Optional.absent(), - {{/vendorExtensions.x-original-is-number}} - {{^vendorExtensions.x-original-is-number}} - {{^isEnum}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present(mapValueOfType<{{#vendorExtensions.x-unwrapped-datatype}}{{{vendorExtensions.x-unwrapped-datatype}}}{{/vendorExtensions.x-unwrapped-datatype}}{{^vendorExtensions.x-unwrapped-datatype}}{{{datatypeWithEnum}}}{{/vendorExtensions.x-unwrapped-datatype}}>(json, r'{{{baseName}}}')) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isEnum}} - {{#isEnum}} - {{#vendorExtensions.x-is-optional}} - {{{name}}}: json.containsKey(r'{{{baseName}}}') ? Optional.present({{{enumName}}}.fromJson(json[r'{{{baseName}}}'])) : const Optional.absent(), - {{/vendorExtensions.x-is-optional}} - {{^vendorExtensions.x-is-optional}} - {{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? const {{{enumName}}}._({{{.}}}){{/defaultValue}}{{/required}}, - {{/vendorExtensions.x-is-optional}} - {{/isEnum}} - {{/vendorExtensions.x-original-is-number}} - {{/vendorExtensions.x-original-is-integer}} - {{/isNumber}} - {{/isMap}} - {{/isArray}} - {{/complexType}} - {{/isDate}} - {{/isDateTime}} - {{/vars}} - ); - } - return null; - } - - static List<{{{classname}}}> listFromJson(dynamic json, {bool growable = false,}) { - final result = <{{{classname}}}>[]; - if (json is List && json.isNotEmpty) { - for (final row in json) { - final value = {{{classname}}}.fromJson(row); - if (value != null) { - result.add(value); - } - } - } - return result.toList(growable: growable); - } - - static Map mapFromJson(dynamic json) { - final map = {}; - if (json is Map && json.isNotEmpty) { - json = json.cast(); // ignore: parameter_assignments - for (final entry in json.entries) { - final value = {{{classname}}}.fromJson(entry.value); - if (value != null) { - map[entry.key] = value; - } - } - } - return map; - } - - // maps a json object with a list of {{{classname}}}-objects as value to a dart map - static Map> mapListFromJson(dynamic json, {bool growable = false,}) { - final map = >{}; - if (json is Map && json.isNotEmpty) { - // ignore: parameter_assignments - json = json.cast(); - for (final entry in json.entries) { - map[entry.key] = {{{classname}}}.listFromJson(entry.value, growable: growable,); - } - } - return map; - } - - /// The list of required keys that must be present in a JSON. - static const requiredKeys = { -{{#vars}} - {{#required}} - '{{{baseName}}}', - {{/required}} -{{/vars}} - }; -} -{{#vars}} - {{^isModel}} - {{#isEnum}} - {{^isContainer}} - -{{>serialization/native/native_enum_inline}} - - {{/isContainer}} - {{#isContainer}} - {{#mostInnerItems}} - -{{>serialization/native/native_enum_inline}} - - {{/mostInnerItems}} - {{/isContainer}} - {{/isEnum}} - {{/isModel}} -{{/vars}}