From 87660a3ef2a4f884346594f9914f62394775d68e Mon Sep 17 00:00:00 2001 From: Adam Basfop Cavendish Date: Wed, 11 Nov 2026 19:51:02 +0800 Subject: [PATCH] fix: preserve Java untagged-union member payloads - Select Java OkHttp union members using retained finite scalar and structural facts so unconstrained Gson fields cannot swallow another member's payload. - Expose firstMatch, strict, and explicit raw policies while keeping typed sealed interfaces, record payloads, and existing root-null behavior by default. - Count original concrete members before opaque fallbacks and decode the selected member once, preserving nested strict errors and authored wrapper boundaries. - Compare exact significands and unbounded exponents instead of Gson's scale- limited decimal accessor, without rounding or hiding native decoder failures. - Memoize recursive predicates by JSON node identity with qualified boxed types, retaining failed matches and models named Integer or Boolean without collisions. - Preserve complete raw JSON trees, including null entries and large numbers, without leaking a changed null policy into ordinary sibling serialization. - Build adapter declarations with TypeSpec and semantic fields, methods and types, limiting the unsupported final-class modifier escape to its single token. - Keep ordinary and tagged codecs unchanged and exercise generated model and OkHttp response/error consumers through independent native policy projects. --- CHANGELOG.md | 7 + docs/src/SUMMARY.md | 1 + docs/src/java_config.md | 62 ++ scripts/generate-jvm-client-fixtures.sh | 35 +- src/generators/java/okhttp/codegen.rs | 1 + src/generators/java/okhttp/config.rs | 37 ++ src/generators/java/okhttp/matching.rs | 614 ++++++++++++++++++ src/generators/java/okhttp/mod.rs | 2 + src/generators/java/okhttp/sigil_emit.rs | 317 +++------ src/generators/java/okhttp/union.rs | 180 +++++ src/generators/java/okhttp/util.rs | 15 +- .../valid/java-union-helper-types.yaml | 16 + tests/fixtures/valid/java-union-policies.yaml | 213 ++++++ tests/generated-clients/jvm/README.md | 8 + .../java/cases/union-first-match/build.gradle | 2 + .../cases/union-first-match/gradle.lockfile | 22 + .../src/test/java/UnionPolicyTest.java | 3 + .../union-helper-types-strict/build.gradle | 1 + .../union-helper-types-strict/gradle.lockfile | 22 + .../src/test/java/HelperTypesTest.java | 1 + .../cases/union-helper-types/build.gradle | 1 + .../cases/union-helper-types/gradle.lockfile | 22 + .../src/test/java/HelperTypesTest.java | 1 + .../jvm/java/cases/union-raw/build.gradle | 1 + .../jvm/java/cases/union-raw/gradle.lockfile | 22 + .../src/test/java/RawUnionPolicyTest.java | 74 +++ .../cases/union-selection/gradle.lockfile | 22 + .../src/test/java/UnionSelectionTest.java | 62 ++ .../jvm/java/cases/union-strict/build.gradle | 2 + .../java/cases/union-strict/gradle.lockfile | 22 + .../src/test/java/UnionPolicyTest.java | 3 + tests/generated-clients/jvm/java/fixtures.txt | 7 + .../RecursiveHelperTypeCases.java | 17 + .../support/http/UnionHttpTestSupport.java | 18 + .../union-policies/TypedUnionPolicyCases.java | 244 +++++++ .../jvm/settings-common.gradle | 19 +- .../models/AnyOfTypes.java.golden | 21 +- .../models/OneOfObjects.java.golden | 96 ++- .../models/OneOfTypes.java.golden | 30 +- .../models/OneOfWithNull.java.golden | 57 +- .../enum-repr/models/MixedEnum.java.golden | 112 +++- .../enum-repr/models/UntaggedEnum.java.golden | 72 +- .../models/Choice.java.golden | 69 +- .../models/Foo.java.golden | 82 ++- .../models/Foo.java.golden | 72 +- .../models/Qux.java.golden | 53 +- .../models/Foo.java.golden | 59 +- .../models/Config.java.golden | 29 +- .../models/Foo.java.golden | 91 ++- .../models/Foo.java.golden | 82 ++- .../models/Foo.java.golden | 30 +- .../union-first-match/README.md.golden | 7 + .../apis/GetChoiceBadRequest.java.golden | 40 ++ .../apis/GetChoiceException.java.golden | 33 + .../apis/GetChoiceExceptionDetail.java.golden | 11 + .../apis/GetChoiceResponse.java.golden | 38 ++ .../apis/GetChoiceUnexpected.java.golden | 22 + .../apis/SelectionApi.java.golden | 63 ++ .../union-first-match/build.gradle.golden | 29 + .../models/AliasChoice.java.golden | 114 ++++ .../models/Alpha.java.golden | 27 + .../models/Ambiguous.java.golden | 98 +++ .../models/AmbiguousAlias.java.golden | 17 + .../union-first-match/models/Beta.java.golden | 27 + .../models/BinaryChoice.java.golden | 106 +++ .../models/BranchOne.java.golden | 25 + .../models/BranchTwo.java.golden | 34 + .../models/ChoiceEnvelope.java.golden | 49 ++ .../models/ContainerChoice.java.golden | 176 +++++ .../models/ContainerOne.java.golden | 19 + .../models/ContainerTwo.java.golden | 28 + .../models/ExcludedNull.java.golden | 17 + .../models/FiniteChoice.java.golden | 299 +++++++++ .../models/FiniteOne.java.golden | 42 ++ .../models/FiniteTwo.java.golden | 42 ++ .../models/FormatChoice.java.golden | 114 ++++ .../models/FormatFallback.java.golden | 96 +++ .../models/IncludedNull.java.golden | 26 + .../models/IntegerFallback.java.golden | 85 +++ .../models/LargeChoice.java.golden | 152 +++++ .../models/LargeOne.java.golden | 17 + .../models/LargeTwo.java.golden | 26 + .../models/LeftPart.java.golden | 20 + .../models/Narrow.java.golden | 17 + .../models/NullableChoice.java.golden | 81 +++ .../models/NullableFields.java.golden | 184 ++++++ .../models/NumberAlias.java.golden | 17 + .../models/NumericFinite.java.golden | 117 ++++ .../models/NumericZero.java.golden | 117 ++++ .../models/OpaqueFirst.java.golden | 203 ++++++ .../models/OpenNested.java.golden | 119 ++++ .../models/RecursiveChoice.java.golden | 67 ++ .../models/RightPart.java.golden | 20 + .../models/ScalarChoice.java.golden | 196 ++++++ .../union-first-match/models/Tree.java.golden | 204 ++++++ .../models/VariantRaw.java.golden | 98 +++ .../union-first-match/models/Wide.java.golden | 26 + .../models/WrappedChoice.java.golden | 163 +++++ .../models/WrappedPair.java.golden | 23 + .../runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../runtime/BearerAuth.java.golden | 29 + .../README.md.golden | 7 + .../build.gradle.golden | 29 + .../models/Boolean.java.golden | 17 + .../models/Branch.java.golden | 19 + .../models/Integer.java.golden | 17 + .../models/Tree.java.golden | 132 ++++ .../runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../runtime/BearerAuth.java.golden | 29 + .../union-helper-types/README.md.golden | 7 + .../union-helper-types/build.gradle.golden | 29 + .../models/Boolean.java.golden | 17 + .../models/Branch.java.golden | 19 + .../models/Integer.java.golden | 17 + .../models/Tree.java.golden | 126 ++++ .../runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../runtime/BearerAuth.java.golden | 29 + .../java-okhttp/union-raw/README.md.golden | 7 + .../apis/GetChoiceBadRequest.java.golden | 40 ++ .../apis/GetChoiceException.java.golden | 33 + .../apis/GetChoiceExceptionDetail.java.golden | 11 + .../apis/GetChoiceResponse.java.golden | 38 ++ .../apis/GetChoiceUnexpected.java.golden | 22 + .../union-raw/apis/SelectionApi.java.golden | 63 ++ .../java-okhttp/union-raw/build.gradle.golden | 29 + .../union-raw/models/AliasChoice.java.golden | 55 ++ .../union-raw/models/Alpha.java.golden | 27 + .../union-raw/models/Ambiguous.java.golden | 55 ++ .../models/AmbiguousAlias.java.golden | 17 + .../union-raw/models/Beta.java.golden | 27 + .../union-raw/models/BinaryChoice.java.golden | 55 ++ .../union-raw/models/BranchOne.java.golden | 25 + .../union-raw/models/BranchTwo.java.golden | 34 + .../models/ChoiceEnvelope.java.golden | 49 ++ .../models/ContainerChoice.java.golden | 55 ++ .../union-raw/models/ContainerOne.java.golden | 19 + .../union-raw/models/ContainerTwo.java.golden | 28 + .../union-raw/models/ExcludedNull.java.golden | 17 + .../union-raw/models/FiniteChoice.java.golden | 55 ++ .../union-raw/models/FiniteOne.java.golden | 42 ++ .../union-raw/models/FiniteTwo.java.golden | 42 ++ .../union-raw/models/FormatChoice.java.golden | 55 ++ .../models/FormatFallback.java.golden | 55 ++ .../union-raw/models/IncludedNull.java.golden | 26 + .../models/IntegerFallback.java.golden | 55 ++ .../union-raw/models/LargeChoice.java.golden | 55 ++ .../union-raw/models/LargeOne.java.golden | 17 + .../union-raw/models/LargeTwo.java.golden | 26 + .../union-raw/models/LeftPart.java.golden | 20 + .../union-raw/models/Narrow.java.golden | 17 + .../models/NullableChoice.java.golden | 55 ++ .../models/NullableFields.java.golden | 55 ++ .../union-raw/models/NumberAlias.java.golden | 17 + .../models/NumericFinite.java.golden | 55 ++ .../union-raw/models/NumericZero.java.golden | 55 ++ .../union-raw/models/OpaqueFirst.java.golden | 55 ++ .../union-raw/models/OpenNested.java.golden | 55 ++ .../models/RecursiveChoice.java.golden | 55 ++ .../union-raw/models/RightPart.java.golden | 20 + .../union-raw/models/ScalarChoice.java.golden | 55 ++ .../union-raw/models/Tree.java.golden | 55 ++ .../union-raw/models/VariantRaw.java.golden | 55 ++ .../union-raw/models/Wide.java.golden | 26 + .../models/WrappedChoice.java.golden | 55 ++ .../union-raw/models/WrappedPair.java.golden | 23 + .../union-raw/runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../union-raw/runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../union-raw/runtime/BearerAuth.java.golden | 29 + .../union-selection/README.md.golden | 7 + .../union-selection/build.gradle.golden | 29 + .../union-selection/models/Choice.java.golden | 155 +++++ .../models/ClientTool.java.golden | 28 + .../models/Envelope.java.golden | 36 + .../models/NumericOne.java.golden | 26 + .../models/NumericTwo.java.golden | 26 + .../models/Request.java.golden | 19 + .../models/RequiredChoice.java.golden | 155 +++++ .../models/RequiredOne.java.golden | 26 + .../models/RequiredTwo.java.golden | 26 + .../models/SearchTool.java.golden | 32 + .../union-selection/models/Tool.java.golden | 140 ++++ .../runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../runtime/BearerAuth.java.golden | 29 + .../java-okhttp/union-strict/README.md.golden | 7 + .../apis/GetChoiceBadRequest.java.golden | 40 ++ .../apis/GetChoiceException.java.golden | 33 + .../apis/GetChoiceExceptionDetail.java.golden | 11 + .../apis/GetChoiceResponse.java.golden | 38 ++ .../apis/GetChoiceUnexpected.java.golden | 22 + .../apis/SelectionApi.java.golden | 63 ++ .../union-strict/build.gradle.golden | 29 + .../models/AliasChoice.java.golden | 120 ++++ .../union-strict/models/Alpha.java.golden | 27 + .../union-strict/models/Ambiguous.java.golden | 104 +++ .../models/AmbiguousAlias.java.golden | 17 + .../union-strict/models/Beta.java.golden | 27 + .../models/BinaryChoice.java.golden | 112 ++++ .../union-strict/models/BranchOne.java.golden | 25 + .../union-strict/models/BranchTwo.java.golden | 34 + .../models/ChoiceEnvelope.java.golden | 49 ++ .../models/ContainerChoice.java.golden | 182 ++++++ .../models/ContainerOne.java.golden | 19 + .../models/ContainerTwo.java.golden | 28 + .../models/ExcludedNull.java.golden | 17 + .../models/FiniteChoice.java.golden | 305 +++++++++ .../union-strict/models/FiniteOne.java.golden | 42 ++ .../union-strict/models/FiniteTwo.java.golden | 42 ++ .../models/FormatChoice.java.golden | 120 ++++ .../models/FormatFallback.java.golden | 99 +++ .../models/IncludedNull.java.golden | 26 + .../models/IntegerFallback.java.golden | 88 +++ .../models/LargeChoice.java.golden | 158 +++++ .../union-strict/models/LargeOne.java.golden | 17 + .../union-strict/models/LargeTwo.java.golden | 26 + .../union-strict/models/LeftPart.java.golden | 20 + .../union-strict/models/Narrow.java.golden | 17 + .../models/NullableChoice.java.golden | 84 +++ .../models/NullableFields.java.golden | 190 ++++++ .../models/NumberAlias.java.golden | 17 + .../models/NumericFinite.java.golden | 120 ++++ .../models/NumericZero.java.golden | 120 ++++ .../models/OpaqueFirst.java.golden | 206 ++++++ .../models/OpenNested.java.golden | 122 ++++ .../models/RecursiveChoice.java.golden | 70 ++ .../union-strict/models/RightPart.java.golden | 20 + .../models/ScalarChoice.java.golden | 205 ++++++ .../union-strict/models/Tree.java.golden | 210 ++++++ .../models/VariantRaw.java.golden | 104 +++ .../union-strict/models/Wide.java.golden | 26 + .../models/WrappedChoice.java.golden | 169 +++++ .../models/WrappedPair.java.golden | 23 + .../runtime/ApiClient.java.golden | 78 +++ .../runtime/ApiException.java.golden | 30 + .../runtime/ApiKeyAuth.java.golden | 41 ++ .../runtime/ApiKeyLocation.java.golden | 10 + .../runtime/Authenticator.java.golden | 11 + .../runtime/BearerAuth.java.golden | 29 + tests/golden_tests_java_okhttp.rs | 37 ++ tests/ir_scalar_enums.rs | 16 +- 258 files changed, 14191 insertions(+), 311 deletions(-) create mode 100644 docs/src/java_config.md create mode 100644 src/generators/java/okhttp/matching.rs create mode 100644 src/generators/java/okhttp/union.rs create mode 100644 tests/fixtures/valid/java-union-helper-types.yaml create mode 100644 tests/fixtures/valid/java-union-policies.yaml create mode 100644 tests/generated-clients/jvm/java/cases/union-first-match/build.gradle create mode 100644 tests/generated-clients/jvm/java/cases/union-first-match/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-first-match/src/test/java/UnionPolicyTest.java create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types-strict/build.gradle create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types-strict/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types-strict/src/test/java/HelperTypesTest.java create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types/build.gradle create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-helper-types/src/test/java/HelperTypesTest.java create mode 100644 tests/generated-clients/jvm/java/cases/union-raw/build.gradle create mode 100644 tests/generated-clients/jvm/java/cases/union-raw/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-raw/src/test/java/RawUnionPolicyTest.java create mode 100644 tests/generated-clients/jvm/java/cases/union-selection/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-selection/src/test/java/UnionSelectionTest.java create mode 100644 tests/generated-clients/jvm/java/cases/union-strict/build.gradle create mode 100644 tests/generated-clients/jvm/java/cases/union-strict/gradle.lockfile create mode 100644 tests/generated-clients/jvm/java/cases/union-strict/src/test/java/UnionPolicyTest.java create mode 100644 tests/generated-clients/jvm/java/fixtures.txt create mode 100644 tests/generated-clients/jvm/java/support/helper-types/RecursiveHelperTypeCases.java create mode 100644 tests/generated-clients/jvm/java/support/http/UnionHttpTestSupport.java create mode 100644 tests/generated-clients/jvm/java/support/union-policies/TypedUnionPolicyCases.java create mode 100644 tests/golden/java/java-okhttp/union-first-match/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceBadRequest.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceExceptionDetail.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceResponse.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceUnexpected.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/apis/SelectionApi.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/AliasChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Alpha.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Ambiguous.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/AmbiguousAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Beta.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/BinaryChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/BranchOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/BranchTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ChoiceEnvelope.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ContainerChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ContainerOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ContainerTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ExcludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/FiniteChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/FiniteOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/FiniteTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/FormatChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/FormatFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/IncludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/IntegerFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/LargeChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/LargeOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/LargeTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/LeftPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Narrow.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/NullableChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/NullableFields.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/NumberAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/NumericFinite.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/NumericZero.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/OpaqueFirst.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/OpenNested.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/RecursiveChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/RightPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/ScalarChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Tree.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/VariantRaw.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/Wide.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/WrappedChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/models/WrappedPair.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-first-match/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/models/Boolean.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/models/Branch.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/models/Integer.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/models/Tree.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types-strict/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/models/Boolean.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/models/Branch.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/models/Integer.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/models/Tree.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-helper-types/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/GetChoiceBadRequest.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/GetChoiceException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/GetChoiceExceptionDetail.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/GetChoiceResponse.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/GetChoiceUnexpected.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/apis/SelectionApi.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/AliasChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Alpha.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Ambiguous.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/AmbiguousAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Beta.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/BinaryChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/BranchOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/BranchTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ChoiceEnvelope.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ContainerChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ContainerOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ContainerTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ExcludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/FiniteChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/FiniteOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/FiniteTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/FormatChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/FormatFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/IncludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/IntegerFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/LargeChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/LargeOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/LargeTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/LeftPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Narrow.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/NullableChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/NullableFields.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/NumberAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/NumericFinite.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/NumericZero.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/OpaqueFirst.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/OpenNested.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/RecursiveChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/RightPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/ScalarChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Tree.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/VariantRaw.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/Wide.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/WrappedChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/models/WrappedPair.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-raw/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/Choice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/ClientTool.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/Envelope.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/NumericOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/NumericTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/Request.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/RequiredChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/RequiredOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/RequiredTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/SearchTool.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/models/Tool.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-selection/runtime/BearerAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/README.md.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/GetChoiceBadRequest.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/GetChoiceException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/GetChoiceExceptionDetail.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/GetChoiceResponse.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/GetChoiceUnexpected.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/apis/SelectionApi.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/build.gradle.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/AliasChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Alpha.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Ambiguous.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/AmbiguousAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Beta.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/BinaryChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/BranchOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/BranchTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ChoiceEnvelope.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ContainerChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ContainerOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ContainerTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ExcludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/FiniteChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/FiniteOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/FiniteTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/FormatChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/FormatFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/IncludedNull.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/IntegerFallback.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/LargeChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/LargeOne.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/LargeTwo.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/LeftPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Narrow.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/NullableChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/NullableFields.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/NumberAlias.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/NumericFinite.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/NumericZero.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/OpaqueFirst.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/OpenNested.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/RecursiveChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/RightPart.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/ScalarChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Tree.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/VariantRaw.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/Wide.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/WrappedChoice.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/models/WrappedPair.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/ApiClient.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/ApiException.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyAuth.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyLocation.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/Authenticator.java.golden create mode 100644 tests/golden/java/java-okhttp/union-strict/runtime/BearerAuth.java.golden diff --git a/CHANGELOG.md b/CHANGELOG.md index c60ca9915..38c79cfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Select Java OkHttp untagged-union members using retained scalar enum and + structural facts instead of unconstrained Gson dispatch. Preserve typed member + payloads in models, containers, recursive values, and HTTP response/error bodies. + Add `firstMatch`, `strict`, and explicit `VariantRaw(JsonElement)` policies, + retaining existing typed APIs, Gson codecs, null handling, and wrapper boundaries. + Match scientific notation without Gson's decimal scale limit and keep recursive + helpers independent of model names such as `Integer` and `Boolean`. - Select Rust untagged-union members using retained scalar enum and structural facts instead of Serde's unconstrained first-success dispatch. Preserve typed variants and payloads across reqwest, ureq, and aioduct, including nested diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 11d3c9243..796c21068 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Rust Generator Configuration](rust_config.md) - [TypeScript Generator Configuration](ts_config.md) - [Python Generator Configuration](python_config.md) +- [Java Generator Configuration](java_config.md) - [Architecture](architecture.md) - [Adding a Generator](adding_a_generator.md) - [IR Reference](ir_reference.md) diff --git a/docs/src/java_config.md b/docs/src/java_config.md new file mode 100644 index 000000000..c448d10b1 --- /dev/null +++ b/docs/src/java_config.md @@ -0,0 +1,62 @@ +# Java Generator Configuration + +The `java-okhttp` backend emits Java 17 sealed interfaces and records for unions, +with Gson adapters that retain the selected member's declared payload. + +## Untagged union selection + +```toml +[java-okhttp] +package_name = "com.example.sdk" +untagged_union_strategy = "firstMatch" +``` + +| Policy | Behavior | +| --- | --- | +| `firstMatch` (default) | Keep typed variants; choose the first structural match in schema order, with concrete members before opaque fallbacks. | +| `strict` | Keep the same typed API; reject multiple authored concrete matches before decoding. Use an opaque fallback only when no concrete member matches. | +| `raw` | Explicitly emit only `VariantRaw(JsonElement)`, preserving known, ambiguous, unmatched, and null JSON without typed selection. | + +Both typed policies reject unmatched non-null values. Matching uses required wire +keys, finite scalar constraints (including numeric and boolean enums), nested +arrays and maps, and productive recursive references. Finite numbers use exact +`BigDecimal` significands and `BigInteger` exponents, retaining +distinctions above double precision without Gson's decimal-accessor scale limit. +Recursive checks share results by JSON node identity and predicate within one +selection, not across decoded values. Private recursive-cache types are qualified +so models named `Integer` or +`Boolean` do not shadow the runtime's boxed types. + +After selection, the original member is decoded once through its existing Gson +codec. Its errors, including nested strict-union errors, propagate rather than +being hidden by another member or opaque fallback. This is structural selection, +not full JSON Schema validation: formats, numeric bounds, and ordinary Gson +coercion or overflow behavior are not redesigned. Serialization does not add +schema validation. + +### Null and representation compatibility + +Typed policies preserve Java's existing root-null API: JSON `null` becomes Java +`null` for a nonnullable union, or the existing `VariantNull.INSTANCE` for a +nullable union. No synthetic raw variant is added to typed unions. Explicit raw +mode instead wraps JSON null in `VariantRaw(JsonNull.INSTANCE)`. +As with typed cases, a case name equal to its enclosing interface is +disambiguated (for example, `VariantRaw.VariantRawVariant`). + +Java aliases remain objects with a `value` field, and `allOf` models keep their +existing member-wrapper fields. Matchers follow those wire boundaries; this is +not an alias-codec or multi-member `allOf` redesign. Tagged unions and ordinary +models keep their existing APIs and codecs. + +Typed variants retain declared member payloads, not arbitrary unknown fields. +Dictionary/additional-property repairs remain separate. Use raw mode when all +JSON must be retained, including unknown keys, null-valued object entries, and +large numeric literals. Raw writing restores the surrounding Gson writer's null +policy; ordinary sibling model fields keep their normal null omission. + +The policy can also be set through the CLI: + +```sh +openapi-nexus generate --input api.yaml --output sdk --generators java-okhttp \ + --generator-config java-okhttp.untagged_union_strategy=strict +``` diff --git a/scripts/generate-jvm-client-fixtures.sh b/scripts/generate-jvm-client-fixtures.sh index 78e557df6..4c6cad61a 100644 --- a/scripts/generate-jvm-client-fixtures.sh +++ b/scripts/generate-jvm-client-fixtures.sh @@ -9,15 +9,26 @@ esac root="$(cd "$(dirname "$0")/.." && pwd)" project="$root/tests/generated-clients/jvm/$backend" -while read -r name fixture; do - case "$name" in - ""|\#*) continue ;; - *[!a-zA-Z0-9_-]*) echo "invalid fixture name: $name" >&2; exit 1 ;; - esac - output="$project/generated/$name" - # Only these fixed, ignored fixture output directories are disposable. - rm -rf "$output" - cargo run --quiet --manifest-path "$root/Cargo.toml" --bin openapi-nexus -- generate \ - --config "$root/tests/generated-clients/config.toml" \ - --input "$root/tests/fixtures/$fixture" --output "$output" --generators "$backend-okhttp" -done < "$root/tests/generated-clients/jvm/fixtures.txt" +manifests=("$root/tests/generated-clients/jvm/fixtures.txt") +if [ -f "$project/fixtures.txt" ]; then manifests+=("$project/fixtures.txt"); fi +for manifest in "${manifests[@]}"; do + while read -r name fixture strategy; do + case "$name" in + ""|\#*) continue ;; + *[!a-zA-Z0-9_-]*) echo "invalid fixture name: $name" >&2; exit 1 ;; + esac + output="$project/generated/$name" + options=(--config "$root/tests/generated-clients/config.toml" + --input "$root/tests/fixtures/$fixture" --output "$output" --generators "$backend-okhttp") + if [ -n "$strategy" ]; then + case "$strategy" in + firstMatch|strict|raw) ;; + *) echo "invalid union strategy: $strategy" >&2; exit 1 ;; + esac + options+=(--generator-config "$backend-okhttp.untagged_union_strategy=$strategy") + fi + # Only these fixed, ignored fixture output directories are disposable. + rm -rf "$output" + cargo run --quiet --manifest-path "$root/Cargo.toml" --bin openapi-nexus -- generate "${options[@]}" + done < "$manifest" +done diff --git a/src/generators/java/okhttp/codegen.rs b/src/generators/java/okhttp/codegen.rs index fd55d3164..42f5405f2 100644 --- a/src/generators/java/okhttp/codegen.rs +++ b/src/generators/java/okhttp/codegen.rs @@ -43,6 +43,7 @@ impl JavaOkhttpCodeGenerator { &package_name, &header, &request_inputs, + self.config.untagged_union_strategy, )?); files.extend(sigil_emit_api::generate_api_files( diff --git a/src/generators/java/okhttp/config.rs b/src/generators/java/okhttp/config.rs index 4da24704a..6731f685a 100644 --- a/src/generators/java/okhttp/config.rs +++ b/src/generators/java/okhttp/config.rs @@ -1,9 +1,23 @@ use serde::{Deserialize, Serialize}; +/// Structural selection policy for generated untagged Gson adapters. +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +pub enum UntaggedUnionStrategy { + #[default] + #[serde(rename = "firstMatch")] + FirstMatch, + #[serde(rename = "strict")] + Strict, + #[serde(rename = "raw")] + Raw, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct JavaOkhttpConfig { #[serde(default)] pub package_name: Option, + #[serde(default)] + pub untagged_union_strategy: UntaggedUnionStrategy, } impl From for JavaOkhttpConfig { @@ -14,3 +28,26 @@ impl From for JavaOkhttpConfig { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn untagged_union_strategy_is_explicit_and_defaults_to_first_match() { + assert_eq!( + JavaOkhttpConfig::default().untagged_union_strategy, + UntaggedUnionStrategy::FirstMatch + ); + for (value, expected) in [ + ("firstMatch", UntaggedUnionStrategy::FirstMatch), + ("strict", UntaggedUnionStrategy::Strict), + ("raw", UntaggedUnionStrategy::Raw), + ] { + let config: JavaOkhttpConfig = + toml::from_str(&format!("untagged_union_strategy = '{value}'")).unwrap(); + assert_eq!(config.untagged_union_strategy, expected); + } + assert!(toml::from_str::("untagged_union_strategy = 'unknown'").is_err()); + } +} diff --git a/src/generators/java/okhttp/matching.rs b/src/generators/java/okhttp/matching.rs new file mode 100644 index 000000000..b8b7f2959 --- /dev/null +++ b/src/generators/java/okhttp/matching.rs @@ -0,0 +1,614 @@ +//! Private Gson predicates for the shared structural union-selection facts. + +use std::borrow::Cow; + +use sigil_stitch::error::SigilStitchError; +use sigil_stitch::prelude::{CodeBlock, FunSpec, ParameterSpec, TypeName, Visibility, sigil_quote}; + +use crate::generators::type_application; +use crate::generators::union_match::{ + JsonShape, MatchScope, accepts_any, plan_named_shape, selection_shapes, +}; +use crate::ir::types::{IrObject, IrProperty, IrSchemaKind, IrSpec}; + +/// Java aliases and intersections have ordinary Gson wrapper fields. Project +/// only matcher facts; their declarations and codecs remain intact. +pub(super) fn wire_ir(ir: &IrSpec) -> Cow<'_, IrSpec> { + if !ir.schemas.values().any(|schema| { + matches!( + schema.kind, + IrSchemaKind::Alias(_) | IrSchemaKind::Intersection(_) + ) + }) { + return Cow::Borrowed(ir); + } + let mut wire = ir.clone(); + for schema in wire.schemas.values_mut() { + let properties = match &schema.kind { + IrSchemaKind::Alias(expr) => vec![("value".into(), expr)], + IrSchemaKind::Intersection(intersection) => { + super::util::intersection_fields(intersection) + } + _ => continue, + } + .into_iter() + .map(|(name, expr)| { + ( + name.clone(), + IrProperty { + name, + type_expr: expr.clone(), + required: true, + nullable: false, + description: None, + default_value: None, + format: None, + validation: None, + }, + ) + }) + .collect(); + schema.kind = IrSchemaKind::Object(IrObject { + properties, + additional_properties: None, + }); + } + Cow::Owned(wire) +} + +#[derive(Default)] +pub(super) struct MatchPlan { + shapes: Vec, +} + +impl MatchPlan { + fn constant(shape: &JsonShape) -> Option { + match shape { + JsonShape::Any => Some(true), + JsonShape::Never => Some(false), + JsonShape::Reference { + scope: MatchScope::Any, + unrestricted: true, + .. + } => Some(true), + JsonShape::AnyOf(_) | JsonShape::AllOf(_) | JsonShape::Nullable(_) + if accepts_any(shape) => + { + Some(true) + } + JsonShape::Literal(serde_json::Value::Array(_) | serde_json::Value::Object(_)) => { + Some(false) + } + _ => None, + } + } + + pub(super) fn register(&mut self, shape: &JsonShape, ir: &IrSpec) { + if Self::constant(shape).is_some() || self.shapes.contains(shape) { + return; + } + self.shapes.push(shape.clone()); + match shape { + JsonShape::Nullable(inner) | JsonShape::Array(inner) | JsonShape::Map(inner) => { + self.register(inner, ir) + } + JsonShape::MapWithConcreteValue { values, concrete } => { + self.register(&JsonShape::Map(values.clone()), ir); + self.register(concrete, ir); + } + JsonShape::Object(properties) => { + for property in properties { + self.register(&property.shape, ir); + } + } + JsonShape::ObjectWithAdditional { properties, values } => { + self.register(&JsonShape::Object(properties.clone()), ir); + self.register(values, ir); + } + JsonShape::AnyOf(children) | JsonShape::AllOf(children) => { + for child in children { + self.register(child, ir); + } + } + JsonShape::Reference { schema, scope, .. } => { + let shape = plan_named_shape(schema, ir); + if *scope == MatchScope::Any { + self.register(&shape, ir); + } else { + let (concrete, fallback) = selection_shapes(shape); + self.register(&concrete, ir); + if *scope == MatchScope::Fallback { + self.register(&fallback, ir); + } + } + } + _ => {} + } + } + + fn index(&self, shape: &JsonShape) -> usize { + self.shapes + .iter() + .position(|candidate| candidate == shape) + .expect("registered Java union shape") + } + + fn symbol(&self, shape: &JsonShape) -> String { + format!("matches{}", self.index(shape)) + } + + pub(super) fn is_recursive(&self) -> bool { + self.shapes + .iter() + .any(|shape| matches!(shape, JsonShape::Reference { .. })) + } + + fn recursive(shape: &JsonShape) -> bool { + if Self::constant(shape).is_some() { + return false; + } + match shape { + JsonShape::Reference { .. } => true, + JsonShape::Nullable(inner) | JsonShape::Array(inner) | JsonShape::Map(inner) => { + Self::recursive(inner) + } + JsonShape::MapWithConcreteValue { values, concrete } => { + Self::recursive(values) || Self::recursive(concrete) + } + JsonShape::Object(properties) => properties + .iter() + .any(|property| Self::recursive(&property.shape)), + JsonShape::ObjectWithAdditional { properties, values } => { + properties + .iter() + .any(|property| Self::recursive(&property.shape)) + || Self::recursive(values) + } + JsonShape::AnyOf(children) | JsonShape::AllOf(children) => { + children.iter().any(Self::recursive) + } + _ => false, + } + } + + pub(super) fn memo_type() -> TypeName { + type_application( + TypeName::qualified("java.util", "IdentityHashMap"), + vec![ + TypeName::importable("com.google.gson", "JsonElement"), + Self::results_type(), + ], + ) + } + + fn results_type() -> TypeName { + type_application( + TypeName::qualified("java.util", "Map"), + vec![ + TypeName::qualified("java.lang", "Integer"), + TypeName::qualified("java.lang", "Boolean"), + ], + ) + } + + pub(super) fn condition( + &self, + shape: &JsonShape, + candidate: CodeBlock, + ) -> Result { + match Self::constant(shape) { + Some(true) => sigil_quote!(Java { true }), + Some(false) => sigil_quote!(Java { false }), + None => { + sigil_quote!(Java { $N(self.symbol(shape))($C(candidate)$if(Self::recursive(shape)) { , memo }) }) + } + } + } + + pub(super) fn functions(&self, ir: &IrSpec) -> Result, SigilStitchError> { + let mut functions = self + .shapes + .iter() + .map(|shape| { + let mut function = FunSpec::builder(&self.symbol(shape)) + .visibility(Visibility::Private) + .is_static() + .add_param(ParameterSpec::of( + "candidate", + TypeName::importable("com.google.gson", "JsonElement"), + )); + if Self::recursive(shape) { + function = function.add_param(ParameterSpec::of("memo", Self::memo_type())); + } + function + .returns(vec![TypeName::primitive("boolean")]) + .body(self.body(shape, ir)?) + .build() + }) + .collect::, _>>()?; + functions.extend(self.numeric_functions()?); + Ok(functions) + } + + fn numeric_functions(&self) -> Result, SigilStitchError> { + if !self.shapes.iter().any(|shape| { + matches!( + shape, + JsonShape::Number { integer: true } + | JsonShape::Binary + | JsonShape::Literal(serde_json::Value::Number(_)) + ) + }) { + return Ok(Vec::new()); + } + let decimal = TypeName::qualified("java.math", "BigDecimal"); + let integer = TypeName::qualified("java.math", "BigInteger"); + let text = TypeName::qualified("java.lang", "String"); + let math = TypeName::qualified("java.lang", "Math"); + // Gson's decimal accessor limits absolute scale to 9999. Read only the + // significand as BigDecimal and keep the exponent as BigInteger, so + // selection stays exact without adding a numeric limit to the codec. + let significand = FunSpec::builder("numberSignificand") + .visibility(Visibility::Private) + .is_static() + .add_param(ParameterSpec::of("literal", text.clone())) + .returns(vec![decimal.clone()]) + .body(sigil_quote!(Java { + int exponent = $T(math.clone()).max(literal.indexOf('e'), literal.indexOf('E')); + return new $T(decimal.clone())(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + })?) + .build()?; + let exponent = FunSpec::builder("numberExponent") + .visibility(Visibility::Private) + .is_static() + .add_param(ParameterSpec::of("literal", text.clone())) + .returns(vec![integer.clone()]) + .body(sigil_quote!(Java { + int exponent = $T(math).max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? $T(integer.clone()).ZERO : new $T(integer.clone())(literal.substring(exponent + 1)); + })?) + .build()?; + let mut functions = vec![significand, exponent]; + if self + .shapes + .iter() + .any(|shape| matches!(shape, JsonShape::Literal(serde_json::Value::Number(_)))) + { + functions.push(FunSpec::builder("matchesNumber") + .visibility(Visibility::Private) + .is_static() + .add_param(ParameterSpec::of("candidate", TypeName::importable("com.google.gson", "JsonElement"))) + .add_param(ParameterSpec::of("expected", text.clone())) + .returns(vec![TypeName::primitive("boolean")]) + .body(sigil_quote!(Java { + $T(text) literal = candidate.getAsString(); + $T(decimal.clone()) significand = numberSignificand(literal); + $T(decimal.clone()) expectedNumber = new $T(decimal)(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { return false; } + return significand.signum() == 0 $+ + || numberExponent(literal).equals($T(integer).valueOf((long) significand.scale() - expectedNumber.scale())); + })?) + .build()?); + } + Ok(functions) + } + + fn body(&self, shape: &JsonShape, ir: &IrSpec) -> Result { + let element = TypeName::importable("com.google.gson", "JsonElement"); + let decimal = TypeName::qualified("java.math", "BigDecimal"); + let big_integer = TypeName::qualified("java.math", "BigInteger"); + let text = TypeName::qualified("java.lang", "String"); + match shape { + JsonShape::Any => sigil_quote!(Java { return true; }), + JsonShape::Never => sigil_quote!(Java { return false; }), + JsonShape::Null => sigil_quote!(Java { return candidate.isJsonNull(); }), + JsonShape::Boolean => { + sigil_quote!(Java { return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); }) + } + JsonShape::String => { + sigil_quote!(Java { return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); }) + } + JsonShape::Number { integer } => sigil_quote!(Java { + $if(*integer) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { return false; } + $T(text) literal = candidate.getAsString(); + $T(decimal) significand = numberSignificand(literal); + return significand.signum() == 0 $+ + || numberExponent(literal).compareTo($T(big_integer).valueOf(significand.scale())) >= 0; + } $else { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + }), + JsonShape::Binary => sigil_quote!(Java { + if (!candidate.isJsonArray()) { return false; } + for ($T(element) item : candidate.getAsJsonArray()) { + if (!item.isJsonPrimitive() || !item.getAsJsonPrimitive().isNumber()) { return false; } + $T(text) literal = item.getAsString(); + $T(decimal.clone()) significand = numberSignificand(literal); + if (significand.signum() == 0) { continue; } + $T(big_integer.clone()) scale = $T(big_integer.clone()).valueOf(significand.scale()).subtract(numberExponent(literal)); + // A nonzero integral byte has at most two trailing zeros. + if (scale.signum() > 0 || scale.compareTo($T(big_integer).valueOf(-2)) < 0) { return false; } + $T(decimal.clone()) number = new $T(decimal.clone())(significand.unscaledValue(), scale.intValueExact()); + if (number.compareTo(new $T(decimal.clone())("-128")) < 0 + || number.compareTo(new $T(decimal.clone())("255")) > 0) { return false; } + } + return true; + }), + JsonShape::Literal(value) => match value { + serde_json::Value::Null => sigil_quote!(Java { return candidate.isJsonNull(); }), + serde_json::Value::Bool(value) => sigil_quote!(Java { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() $+ + && candidate.getAsBoolean() == $L(value.to_string()); + }), + serde_json::Value::String(value) => sigil_quote!(Java { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() $+ + && candidate.getAsString().equals($S(value)); + }), + serde_json::Value::Number(value) => sigil_quote!(Java { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() $+ + && matchesNumber(candidate, $S(value.to_string())); + }), + _ => sigil_quote!(Java { return false; }), + }, + JsonShape::Nullable(inner) => { + let check = self.condition(inner, sigil_quote!(Java { candidate })?)?; + sigil_quote!(Java { return candidate.isJsonNull() || $C(check); }) + } + JsonShape::Array(inner) | JsonShape::Map(inner) => { + let array = matches!(shape, JsonShape::Array(_)); + let check = self.condition(inner, sigil_quote!(Java { item })?)?; + sigil_quote!(Java { + $if(array) { + if (!candidate.isJsonArray()) { return false; } + for ($T(element) item : candidate.getAsJsonArray()) { + if (!$C(check)) { return false; } + } + } $else { + if (!candidate.isJsonObject()) { return false; } + for ($T(element) item : candidate.getAsJsonObject().asMap().values()) { + if (!$C(check)) { return false; } + } + } + return true; + }) + } + JsonShape::MapWithConcreteValue { values, concrete } => { + let values = self.condition( + &JsonShape::Map(values.clone()), + sigil_quote!(Java { candidate })?, + )?; + let concrete = self.condition(concrete, sigil_quote!(Java { item })?)?; + sigil_quote!(Java { + if (!$C(values)) { return false; } + for ($T(element) item : candidate.getAsJsonObject().asMap().values()) { + if ($C(concrete)) { return true; } + } + return false; + }) + } + JsonShape::Object(properties) => { + let checks = properties.iter().map(|property| { + let key = &property.wire_name; + let check = self.condition(&property.shape, sigil_quote!(Java { object.get($S(key)) })?)?; + if property.required { + sigil_quote!(Java { if (!object.has($S(key)) || !$C(check)) { return false; } }) + } else { + sigil_quote!(Java { if (object.has($S(key)) && !$C(check)) { return false; } }) + } + }).collect::, _>>()?; + let object = TypeName::importable("com.google.gson", "JsonObject"); + sigil_quote!(Java { + if (!candidate.isJsonObject()) { return false; } + $if(!properties.is_empty()) { + $T(object) object = candidate.getAsJsonObject(); + $C_each(checks) + } + return true; + }) + } + JsonShape::ObjectWithAdditional { properties, values } => { + let object = self.condition( + &JsonShape::Object(properties.clone()), + sigil_quote!(Java { candidate })?, + )?; + let check = self.condition(values, sigil_quote!(Java { item.getValue() })?)?; + let keys: Vec<_> = properties + .iter() + .map(|property| &property.wire_name) + .collect(); + let entry = type_application( + TypeName::qualified("java.util.Map", "Entry"), + vec![TypeName::primitive("String"), element], + ); + let strings = type_application( + TypeName::qualified("java.util", "Set"), + vec![TypeName::primitive("String")], + ); + sigil_quote!(Java { + if (!$C(object)) { return false; } + $T(strings) declared = java.util.Set.of($for(key in &keys; separator = ", ") { $S(key) }); + for ($T(entry) item : candidate.getAsJsonObject().entrySet()) { + if (!declared.contains(item.getKey()) && !$C(check)) { return false; } + } + return true; + }) + } + JsonShape::AnyOf(children) | JsonShape::AllOf(children) => { + let disjunction = matches!(shape, JsonShape::AnyOf(_)); + let checks = children + .iter() + .map(|child| { + let check = self.condition(child, sigil_quote!(Java { candidate })?)?; + if disjunction { + sigil_quote!(Java { if ($C(check)) { return true; } }) + } else { + sigil_quote!(Java { if (!$C(check)) { return false; } }) + } + }) + .collect::, _>>()?; + sigil_quote!(Java { + $C_each(checks) + $if(disjunction) { return false; } $else { return true; } + }) + } + JsonShape::Reference { schema, scope, .. } => { + let index = self.index(shape).to_string(); + let named = plan_named_shape(schema, ir); + let expression = if *scope == MatchScope::Any { + self.condition(&named, sigil_quote!(Java { candidate })?)? + } else { + let (concrete, fallback) = selection_shapes(named); + let concrete = self.condition(&concrete, sigil_quote!(Java { candidate })?)?; + if *scope == MatchScope::Concrete { + concrete + } else { + let fallback = + self.condition(&fallback, sigil_quote!(Java { candidate })?)?; + sigil_quote!(Java { (!$C(concrete) && $C(fallback)) })? + } + }; + // Gson trees are immutable during selection. Identity keys avoid + // JsonElement's recursive structural hashing; predicate indices + // keep scopes distinct, including cached false results. + let results = Self::results_type(); + let boolean = TypeName::qualified("java.lang", "Boolean"); + sigil_quote!(Java { + $T(results) results = memo.get(candidate); + if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); + } + $T(boolean) cached = results.get($L(index.clone())); + if (cached != null) { return cached; } + boolean matched = $C(expression); + results.put($L(index), matched); + return matched; + }) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sigil_stitch::assert_rendered; + use sigil_stitch::lang::java::Java; + + fn fixture() -> IrSpec { + crate::ir::lower::lower( + crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/java-union-policies.yaml" + )) + .unwrap(), + ) + .unwrap() + } + + #[test] + fn finite_numeric_predicates_keep_json_kinds_and_exact_decimal_values() { + let shape = JsonShape::Literal(9007199254740993_u64.into()); + assert_rendered!( + Java::new(), + width = 200, + MatchPlan::default().body(&shape, &fixture()).unwrap(), + "return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, \"9007199254740993\");\n" + ); + assert_rendered!( + Java::new(), + width = 200, + MatchPlan::default() + .body(&JsonShape::Literal("wire\"value".into()), &fixture()) + .unwrap(), + "return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals(\"wire\\\"value\");\n" + ); + } + + #[test] + fn numeric_helpers_compare_significands_with_unbounded_exponents() { + use sigil_stitch::spec::modifiers::DeclarationContext; + + let mut plan = MatchPlan::default(); + plan.register(&JsonShape::Literal(1.into()), &fixture()); + let helper = plan.numeric_functions().unwrap().pop().unwrap(); + assert_rendered!( + Java::new(), + width = 200, + helper + .emit(&Java::new(), DeclarationContext::Member) + .unwrap(), + r#"import com.google.gson.JsonElement; + +private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); +} +"# + ); + } + + #[test] + fn recursive_reference_predicates_cache_by_node_identity_and_shape_index() { + let ir = fixture(); + let shape = JsonShape::Reference { + schema: "Tree".into(), + scope: MatchScope::Any, + unrestricted: false, + accepts_null: false, + }; + let mut plan = MatchPlan::default(); + plan.register(&shape, &ir); + assert_rendered!( + Java::new(), + width = 100, + plan.body(&shape, &ir).unwrap(), + r#"java.util.Map results = memo.get(candidate); +if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); +} +java.lang.Boolean cached = results.get(0); +if (cached != null) { + return cached; +} +boolean matched = matches1(candidate, memo); +results.put(0, matched); +return matched; +"# + ); + } + + #[test] + fn matcher_wire_projection_does_not_change_alias_or_intersection_declarations() { + let ir = fixture(); + let wire = wire_ir(&ir); + assert!(matches!( + ir.schemas["NumberAlias"].kind, + IrSchemaKind::Alias(_) + )); + assert!(matches!( + ir.schemas["WrappedPair"].kind, + IrSchemaKind::Intersection(_) + )); + let IrSchemaKind::Object(alias) = &wire.schemas["NumberAlias"].kind else { + panic!("alias wrapper facts") + }; + assert!(alias.properties["value"].required); + let IrSchemaKind::Object(pair) = &wire.schemas["WrappedPair"].kind else { + panic!("composition wrapper facts") + }; + assert_eq!( + pair.properties + .keys() + .map(String::as_str) + .collect::>(), + ["leftPart", "rightPart"] + ); + } +} diff --git a/src/generators/java/okhttp/mod.rs b/src/generators/java/okhttp/mod.rs index d3361e7a6..5f41555c4 100644 --- a/src/generators/java/okhttp/mod.rs +++ b/src/generators/java/okhttp/mod.rs @@ -1,8 +1,10 @@ pub mod codegen; pub mod config; +mod matching; pub mod runtime; pub mod sigil_emit; pub mod sigil_emit_api; +mod union; pub mod util; pub use codegen::JavaOkhttpCodeGenerator; diff --git a/src/generators/java/okhttp/sigil_emit.rs b/src/generators/java/okhttp/sigil_emit.rs index 0c8772453..dda9c836d 100644 --- a/src/generators/java/okhttp/sigil_emit.rs +++ b/src/generators/java/okhttp/sigil_emit.rs @@ -1,5 +1,3 @@ -use std::collections::HashSet; - use crate::codegen::traits::file_writer::FileInfo; use crate::generators::closed_sum::{ ClosedSumCasePlan, ClosedSumEncoding, ClosedSumPlan, plan_tagged_union, plan_union, @@ -12,17 +10,18 @@ use crate::ir::types::{ IrEnum, IrEnumValueType, IrIntersection, IrObject, IrPrimitive, IrSchema, IrSchemaKind, IrSpec, IrTaggedUnion, IrTypeExpr, IrUnion, }; -use heck::{ToLowerCamelCase, ToPascalCase}; +use heck::ToPascalCase; use sigil_stitch::lang::java::Java; use sigil_stitch::prelude::*; use sigil_stitch::spec::closed_sum_case_spec::ClosedSumCaseSpec; use sigil_stitch::spec::closed_sum_spec::ClosedSumSpec; use sigil_stitch::spec::where_spec::TypeArgument; +use super::config::UntaggedUnionStrategy; use super::util::{ - build_java_getter, escape_java_string, java_boxed_type_str, java_field_name, java_getter_name, - java_primitive, java_primitive_boxed, java_type_str, type_uses_list, type_uses_map, - unique_name, + build_java_getter, escape_java_string, intersection_fields, java_boxed_type_str, + java_field_name, java_getter_name, java_primitive, java_primitive_boxed, java_type_str, + type_uses_list, type_uses_map, }; const RENDER_WIDTH: usize = 100; @@ -32,10 +31,12 @@ pub(crate) fn generate_model_files( package_name: &str, header: &str, request_inputs: &RequestInputPlan, + strategy: UntaggedUnionStrategy, ) -> Result, GeneratorError> { let mut files = Vec::new(); + let matching_ir = super::matching::wire_ir(ir); for (_name, schema) in &ir.schemas { - let body = emit_model_body(schema, ir, package_name) + let body = emit_model_body(schema, ir, &matching_ir, package_name, strategy) .map_err(|error| GeneratorError::emission(ArtifactKind::Model, &schema.name, error))?; let class_name = schema.name.to_pascal_case(); let filename = format!("{class_name}.java"); @@ -134,13 +135,15 @@ fn request_input_java_type(field: &RequestInputField) -> String { fn emit_model_body( schema: &IrSchema, ir: &IrSpec, + matching_ir: &IrSpec, package_name: &str, + strategy: UntaggedUnionStrategy, ) -> Result { let file_spec = match &schema.kind { IrSchemaKind::Object(obj) => emit_object(schema, obj, package_name), IrSchemaKind::Enum(en) => emit_enum(schema, en, package_name), IrSchemaKind::Alias(expr) => emit_alias(schema, expr, package_name), - IrSchemaKind::Union(u) => emit_union(schema, u, ir, package_name), + IrSchemaKind::Union(u) => emit_union(schema, u, matching_ir, package_name, strategy), IrSchemaKind::Intersection(i) => emit_intersection(schema, i, package_name), IrSchemaKind::TaggedUnion(tu) => emit_tagged_union(schema, tu, ir, package_name), }?; @@ -395,15 +398,28 @@ fn emit_union( union: &IrUnion, ir: &IrSpec, package_name: &str, + strategy: UntaggedUnionStrategy, ) -> Result { - if union.members.is_empty() && !union.nullable { + if union.members.is_empty() && !union.nullable && strategy != UntaggedUnionStrategy::Raw { return emit_empty_model(schema, package_name); } - emit_closed_sum( - &plan_union(&schema.name, schema.description.as_deref(), union), - &ir.schemas, - package_name, - ) + let mut plan = plan_union(&schema.name, schema.description.as_deref(), union); + if strategy == UntaggedUnionStrategy::Raw { + plan.cases = vec![ClosedSumCasePlan { + name: if plan.name == "VariantRaw" { + "VariantRawVariant" + } else { + "VariantRaw" + } + .into(), + payload: Some(IrTypeExpr::Any), + doc: None, + wire_name: None, + is_null: false, + }]; + } + let adapter = super::union::adapter(schema, &plan, ir, strategy)?; + emit_closed_sum(&plan, package_name, adapter) } // --------------------------------------------------------------------------- @@ -424,15 +440,9 @@ fn emit_intersection( tb = tb.doc(doc); } - let mut used_names: HashSet = HashSet::new(); - let member_bindings: Vec<(String, String)> = inter - .members - .iter() - .map(|member| { - let member_type = java_type_str(member); - let field_name = unique_name(&member_type.to_lower_camel_case(), &mut used_names); - (member_type, field_name) - }) + let member_bindings: Vec<(String, String)> = intersection_fields(inter) + .into_iter() + .map(|(field_name, member)| (java_type_str(member), field_name)) .collect(); if member_bindings .iter() @@ -499,11 +509,9 @@ fn emit_tagged_union( if tu.variants.is_empty() { return emit_empty_model(schema, package_name); } - emit_closed_sum( - &plan_tagged_union(&schema.name, schema.description.as_deref(), tu, ir), - &ir.schemas, - package_name, - ) + let plan = plan_tagged_union(&schema.name, schema.description.as_deref(), tu, ir); + let adapter = java_closed_sum_adapter(&plan)?; + emit_closed_sum(&plan, package_name, adapter) } fn emit_empty_model(schema: &IrSchema, package_name: &str) -> Result { @@ -527,8 +535,8 @@ fn emit_empty_model(schema: &IrSchema, package_name: &str) -> Result, package_name: &str, + adapter: CodeBlock, ) -> Result { let mut sum = ClosedSumSpec::builder(&plan.name).visibility(Visibility::Public); for doc in &plan.docs { @@ -554,9 +562,7 @@ fn emit_closed_sum( for import in java_adapter_imports() { file = file.add_import(import); } - file.add_closed_sum(sum.build()?) - .add_code(java_closed_sum_adapter(plan, schemas)?) - .build() + file.add_closed_sum(sum.build()?).add_code(adapter).build() } fn java_closed_sum_case(case: &ClosedSumCasePlan) -> Result { @@ -614,12 +620,9 @@ fn java_adapter_imports() -> [ImportSpec; 11] { ] } -fn java_closed_sum_adapter( - plan: &ClosedSumPlan, - schemas: &indexmap::IndexMap, -) -> Result { +fn java_closed_sum_adapter(plan: &ClosedSumPlan) -> Result { let read_dispatch = - java_adapter_read_dispatch(plan, schemas).map_err(|error| SigilStitchError::Render { + java_adapter_read_dispatch(plan).map_err(|error| SigilStitchError::Render { context: format!("Java {} Gson adapter read dispatch", plan.name), message: error.to_string(), })?; @@ -662,64 +665,10 @@ fn java_closed_sum_adapter( }) } -fn java_adapter_read_dispatch( - plan: &ClosedSumPlan, - schemas: &indexmap::IndexMap, -) -> Result { +fn java_adapter_read_dispatch(plan: &ClosedSumPlan) -> Result { match &plan.encoding { ClosedSumEncoding::Untagged => { - let null_case = plan.cases.iter().find(|case| case.is_null); - let null_result = match null_case { - Some(case) => sigil_quote!(Java { - return $N(plan.name.as_str()).$N(case.name.as_str()).INSTANCE; - })?, - None => sigil_quote!(Java { return null; })?, - }; - let mut branches = vec![sigil_quote!(Java { - if (element.isJsonNull()) { - $C(null_result) - } - })?]; - let mut catch_all = None; - let cases = plan - .cases - .iter() - .filter(|case| !case.is_null && !is_catch_all_case(case)) - .chain( - plan.cases - .iter() - .filter(|case| !case.is_null && is_catch_all_case(case)), - ); - for case in cases { - let Some(payload) = &case.payload else { - continue; - }; - let condition = java_json_condition(payload, schemas)?; - let source = sigil_quote!(Java { element })?; - let result = java_adapter_read_value(plan, case, payload, source)?; - if is_catch_all_case(case) { - catch_all = Some(result); - } else { - branches.push(sigil_quote!(Java { - if ($C(condition)) { - $C(result) - } - })?); - } - } - let fallback = match catch_all { - Some(result) => result, - None => sigil_quote!(Java { - throw new JsonParseException($S(format!( - "unknown {} union value", - plan.name - ))); - })?, - }; - sigil_quote!(Java { - $C_each(branches) - $C(fallback) - }) + unreachable!("untagged adapters own structural member selection") } ClosedSumEncoding::Internal { discriminator_field, @@ -812,7 +761,7 @@ fn java_external_read_case( }) } -fn java_adapter_read_value( +pub(super) fn java_adapter_read_value( plan: &ClosedSumPlan, case: &ClosedSumCasePlan, payload: &IrTypeExpr, @@ -860,7 +809,7 @@ fn java_adapter_read_value( }) } -fn java_adapter_write_case( +pub(super) fn java_adapter_write_case( plan: &ClosedSumPlan, case: &ClosedSumCasePlan, ) -> Result { @@ -939,142 +888,6 @@ fn java_adapter_write_case( }) } -fn java_json_condition( - expr: &IrTypeExpr, - schemas: &indexmap::IndexMap, -) -> Result { - match expr { - IrTypeExpr::ScalarEnum { representation, .. } => { - java_json_condition(representation, schemas) - } - IrTypeExpr::Primitive( - IrPrimitive::String - | IrPrimitive::StringWithFormat(_) - | IrPrimitive::Date - | IrPrimitive::DateTime - | IrPrimitive::Uuid, - ) - | IrTypeExpr::StringLiteral(_) - | IrTypeExpr::StringEnum(_) => { - let base = sigil_quote!(Java { - element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - })?; - match expr { - IrTypeExpr::StringLiteral(value) => sigil_quote!(Java { - $C(base) && element.getAsString().equals($S(value)) - }), - IrTypeExpr::StringEnum(values) => { - let checks = values - .iter() - .map(|value| sigil_quote!(Java { element.getAsString().equals($S(value)) })) - .collect::, _>>()?; - let Some((first, rest)) = checks.split_first() else { - return sigil_quote!(Java { false }); - }; - let values = java_join_conditions(first.clone(), rest.to_vec(), true)?; - sigil_quote!(Java { ($C(base) && $C(values)) }) - } - _ => Ok(base), - } - } - IrTypeExpr::Primitive(IrPrimitive::Boolean) => { - sigil_quote!(Java { - element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean() - }) - } - IrTypeExpr::Primitive( - IrPrimitive::Integer - | IrPrimitive::IntegerWithFormat(_) - | IrPrimitive::Number - | IrPrimitive::NumberWithFormat(_), - ) => sigil_quote!(Java { - element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - }), - IrTypeExpr::Primitive(IrPrimitive::Binary) => { - sigil_quote!(Java { element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() }) - } - IrTypeExpr::Array(_) => sigil_quote!(Java { element.isJsonArray() }), - IrTypeExpr::Map(_) => sigil_quote!(Java { element.isJsonObject() }), - IrTypeExpr::Nullable(inner) => { - let inner = java_json_condition(inner, schemas)?; - sigil_quote!(Java { element.isJsonNull() || $C(inner) }) - } - IrTypeExpr::Any => sigil_quote!(Java { true }), - IrTypeExpr::Union(members) => { - let conditions = members - .iter() - .map(|member| java_json_condition(member, schemas)) - .collect::, _>>()?; - let Some((first, rest)) = conditions.split_first() else { - return sigil_quote!(Java { false }); - }; - java_join_conditions(first.clone(), rest.to_vec(), true) - } - IrTypeExpr::Named(name) => match schemas.get(name).map(|schema| &schema.kind) { - Some(IrSchemaKind::Object(object)) => { - let required = object - .properties - .iter() - .filter(|(_, property)| property.required) - .map(|(wire_name, _)| wire_name) - .collect::>(); - let checks = required - .iter() - .map(|wire_name| { - sigil_quote!(Java { - element.getAsJsonObject().has($S(wire_name.as_str())) - }) - }) - .collect::, _>>()?; - let object_check = sigil_quote!(Java { element.isJsonObject() })?; - Ok(java_join_conditions(object_check, checks, false)?) - } - Some(IrSchemaKind::Enum(en)) => match en.value_type { - IrEnumValueType::String => sigil_quote!(Java { - element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() - }), - IrEnumValueType::Integer | IrEnumValueType::Number => sigil_quote!(Java { - element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber() - }), - IrEnumValueType::Mixed => sigil_quote!(Java { element.isJsonPrimitive() }), - }, - Some(IrSchemaKind::Alias(inner)) => java_json_condition(inner, schemas), - Some(IrSchemaKind::Union(union)) => { - let conditions = union - .members - .iter() - .map(|member| java_json_condition(member, schemas)) - .collect::, _>>()?; - let Some((first, rest)) = conditions.split_first() else { - return sigil_quote!(Java { false }); - }; - java_join_conditions(first.clone(), rest.to_vec(), true) - } - _ => sigil_quote!(Java { element.isJsonObject() }), - }, - } -} - -fn is_catch_all_case(case: &ClosedSumCasePlan) -> bool { - matches!(case.payload.as_ref(), Some(IrTypeExpr::Any)) -} - -fn java_join_conditions( - first: CodeBlock, - rest: Vec, - disjunction: bool, -) -> Result { - let mut result = first; - for condition in rest { - result = if disjunction { - sigil_quote!(Java { ($C(result) || $C(condition)) })? - } else { - sigil_quote!(Java { ($C(result) && $C(condition)) })? - }; - } - Ok(result) -} - // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -1165,21 +978,51 @@ mod tests { }], encoding: ClosedSumEncoding::Untagged, }; - let adapter = java_closed_sum_adapter(&plan, &indexmap::IndexMap::new()).unwrap(); + let union = IrUnion { + members: vec![IrTypeExpr::Primitive(IrPrimitive::String)], + nullable: false, + }; + let ir = crate::ir::lower::lower( + crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/minimal.yaml" + )) + .unwrap(), + ) + .unwrap(); + let schema = IrSchema { + name: "Choice".into(), + description: None, + deprecated: false, + is_component: true, + kind: IrSchemaKind::Union(union), + }; + let adapter = + super::super::union::adapter(&schema, &plan, &ir, UntaggedUnionStrategy::FirstMatch) + .unwrap(); assert_rendered!( Java::new(), width = 100, adapter, - r#"final class ChoiceJsonAdapter extends TypeAdapter { + r#"import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +final class ChoiceJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Choice read (JsonReader reader) throws java.io.IOException { + public Choice read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected == 0) { return new Choice.VariantString(element.getAsString()); } throw new JsonParseException("unknown Choice union value"); @@ -1196,6 +1039,10 @@ mod tests { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } } "#, ); diff --git a/src/generators/java/okhttp/union.rs b/src/generators/java/okhttp/union.rs new file mode 100644 index 000000000..9bea29fb3 --- /dev/null +++ b/src/generators/java/okhttp/union.rs @@ -0,0 +1,180 @@ +//! Typed untagged Gson adapters with original-member selection boundaries. + +use sigil_stitch::error::SigilStitchError; +use sigil_stitch::lang::java::Java; +use sigil_stitch::prelude::*; + +use crate::generators::closed_sum::ClosedSumPlan; +use crate::generators::type_application; +use crate::generators::union_match::plan_union_members; +use crate::ir::types::{IrSchema, IrSchemaKind, IrSpec}; + +use super::config::UntaggedUnionStrategy; +use super::matching::MatchPlan; +use super::sigil_emit::{java_adapter_read_value, java_adapter_write_case}; + +pub(super) fn adapter( + schema: &IrSchema, + plan: &ClosedSumPlan, + ir: &IrSpec, + strategy: UntaggedUnionStrategy, +) -> Result { + let raw = strategy == UntaggedUnionStrategy::Raw; + let raw_case = &plan.cases[0].name; + let mut matching = MatchPlan::default(); + let root = TypeName::primitive(&plan.name); + let field = FieldSpec::builder("gson", TypeName::importable("com.google.gson", "Gson")) + .visibility(Visibility::Private) + .is_readonly() + .initializer(sigil_quote!(Java { new Gson() })?.__sigil_trim_trailing_newline()) + .build()?; + let read_body = if raw { + sigil_quote!(Java { return new $N(&plan.name).$N(raw_case)(element); })? + } else { + typed_read(schema, plan, ir, strategy, &mut matching)? + }; + let read = FunSpec::builder("read") + .visibility(Visibility::Public) + .annotate(AnnotationSpec::new("Override")) + .add_param(ParameterSpec::of( + "reader", + TypeName::importable("com.google.gson.stream", "JsonReader"), + )) + .returns(vec![root.clone()]) + .throws(vec![TypeName::qualified("java.io", "IOException")]) + .body(sigil_quote!(Java { + JsonElement element = JsonParser.parseReader(reader); + $C(read_body) + })?) + .build()?; + let write_cases = if raw { + Vec::new() + } else { + plan.cases + .iter() + .map(|case| java_adapter_write_case(plan, case)) + .collect::, _>>()? + }; + let write = FunSpec::builder("write") + .visibility(Visibility::Public) + .annotate(AnnotationSpec::new("Override")) + .add_param(ParameterSpec::of( + "writer", + TypeName::importable("com.google.gson.stream", "JsonWriter"), + )) + .add_param(ParameterSpec::of("value", root.clone())) + .returns(vec![TypeName::primitive("void")]) + .throws(vec![TypeName::qualified("java.io", "IOException")]) + .body(sigil_quote!(Java { + if (value == null) { + writer.nullValue(); + return; + } + $if(raw) { + JsonElement element = (($N(&plan.name).$N(raw_case)) value).value0(); + if (element == null) { element = JsonNull.INSTANCE; } + // Raw trees retain null-valued object entries even when Gson's + // ordinary model serializer omits null fields. Use the native + // tree adapter with both stream writers and JsonTreeWriter. + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } finally { + writer.setSerializeNulls(serializeNulls); + } + } $else { + $C_each(write_cases) + } + })?) + .build()?; + let parent = type_application( + TypeName::importable("com.google.gson", "TypeAdapter"), + vec![root], + ); + let name = format!("{}JsonAdapter", plan.name); + let mut adapter = TypeSpec::builder(&name, TypeKind::Class) + .extends(parent) + .add_field(field) + .add_method(read) + .add_method(write); + for helper in matching.functions(ir)? { + adapter = adapter.add_method(helper); + } + let declaration = adapter + .build()? + .emit(&Java::new())? + .into_iter() + .next() + .expect("one Java adapter class declaration"); + // sigil-stitch 0.7.5 lacks a final-class modifier. Keep only that token + // outside TypeSpec so declaration structure and imports remain semantic. + sigil_quote!(Java { final $C(declaration) }) +} + +fn typed_read( + schema: &IrSchema, + plan: &ClosedSumPlan, + ir: &IrSpec, + strategy: UntaggedUnionStrategy, + matching: &mut MatchPlan, +) -> Result { + let IrSchemaKind::Union(union) = &schema.kind else { + unreachable!("untagged selection owns a union schema") + }; + let members = plan_union_members(&schema.name, union, ir); + for member in &members { + matching.register(&member.shape, ir); + } + let null = match plan.cases.iter().find(|case| case.is_null) { + Some(case) => sigil_quote!(Java { return $N(&plan.name).$N(&case.name).INSTANCE; })?, + None => sigil_quote!(Java { return null; })?, + }; + let strict = strategy == UntaggedUnionStrategy::Strict; + let ambiguous = format!( + "ambiguous untagged union {}: multiple concrete members match", + plan.name + ); + let mut selection = Vec::new(); + for member in &members { + let check = matching.condition(&member.shape, sigil_quote!(Java { element })?)?; + let index = member.source_index.to_string(); + selection.push(if strict && !member.fallback { + sigil_quote!(Java { + if ($C(check)) { + if (selected >= 0) { throw new JsonParseException($S(&ambiguous)); } + selected = $L(index); + } + })? + } else { + sigil_quote!(Java { + if (selected < 0 && $C(check)) { selected = $L(index); } + })? + }); + } + let delegates = plan + .cases + .iter() + .enumerate() + .filter_map(|(index, case)| { + case.payload.as_ref().map(|payload| { + let read = + java_adapter_read_value(plan, case, payload, sigil_quote!(Java { element })?)?; + sigil_quote!(Java { + if (selected == $L(index.to_string())) { $C(read) } + }) + }) + }) + .collect::, SigilStitchError>>()?; + let memo = MatchPlan::memo_type(); + sigil_quote!(Java { + if (element.isJsonNull()) { $C(null) } + int selected = -1; + $if(matching.is_recursive()) { + $T(memo.clone()) memo = new $T(memo)(); + } + $C_each(selection) + $C_each(delegates) + throw new JsonParseException($S(format!("unknown {} union value", plan.name))); + }) +} diff --git a/src/generators/java/okhttp/util.rs b/src/generators/java/okhttp/util.rs index 2c48d0cdf..36f2e7d83 100644 --- a/src/generators/java/okhttp/util.rs +++ b/src/generators/java/okhttp/util.rs @@ -1,11 +1,24 @@ use std::collections::HashSet; -use crate::ir::types::{IrPrimitive, IrTypeExpr}; +use crate::ir::types::{IrIntersection, IrPrimitive, IrTypeExpr}; use heck::{ToLowerCamelCase, ToPascalCase}; #[allow(unused_imports)] use sigil_stitch::lang::java::Java; use sigil_stitch::prelude::*; +/// Field names of Java's existing allOf wrapper representation. +pub(super) fn intersection_fields(intersection: &IrIntersection) -> Vec<(String, &IrTypeExpr)> { + let mut used = HashSet::new(); + intersection + .members + .iter() + .map(|member| { + let name = unique_name(&java_type_str(member).to_lower_camel_case(), &mut used); + (name, member) + }) + .collect() +} + pub fn java_type_str(expr: &IrTypeExpr) -> String { match expr { IrTypeExpr::ScalarEnum { representation, .. } => java_type_str(representation), diff --git a/tests/fixtures/valid/java-union-helper-types.yaml b/tests/fixtures/valid/java-union-helper-types.yaml new file mode 100644 index 000000000..8c85cc833 --- /dev/null +++ b/tests/fixtures/valid/java-union-helper-types.yaml @@ -0,0 +1,16 @@ +openapi: 3.1.0 +info: {title: Java union helper types, version: 1.0.0} +paths: {} +components: + schemas: + Boolean: {type: object, properties: {}} + Integer: {type: object, properties: {}} + Branch: + type: object + required: [children] + properties: + children: {type: array, items: {$ref: '#/components/schemas/Tree'}} + Tree: + anyOf: + - {$ref: '#/components/schemas/Branch'} + - {type: string} diff --git a/tests/fixtures/valid/java-union-policies.yaml b/tests/fixtures/valid/java-union-policies.yaml new file mode 100644 index 000000000..821a94fec --- /dev/null +++ b/tests/fixtures/valid/java-union-policies.yaml @@ -0,0 +1,213 @@ +openapi: 3.1.0 +info: {title: Java union policies, version: 1.0.0} +paths: + /choice: + get: + tags: [Selection] + operationId: getChoice + responses: + '200': + description: Selected member + content: + application/json: + schema: {$ref: '#/components/schemas/FiniteChoice'} + '400': + description: Selected error member + content: + application/json: + schema: {$ref: '#/components/schemas/FiniteChoice'} +components: + schemas: + FiniteOne: + type: object + required: [revision, values, flags] + properties: + revision: {type: integer, enum: [1]} + values: {type: array, items: {type: number, enum: [1, 1.5]}} + flags: {type: object, additionalProperties: {type: boolean, enum: [false]}} + first_value: {type: string} + FiniteTwo: + type: object + required: [revision, values, flags] + properties: + revision: {type: integer, enum: [2]} + values: {type: array, items: {type: number, enum: [2, 2.5]}} + flags: {type: object, additionalProperties: {type: boolean, enum: [true]}} + second_value: {type: string} + FiniteChoice: + oneOf: + - {$ref: '#/components/schemas/FiniteOne'} + - {$ref: '#/components/schemas/FiniteTwo'} + ContainerOne: + type: object + required: [values] + properties: + values: {type: array, items: {type: integer, enum: [1]}} + ContainerTwo: + type: object + required: [values] + properties: + values: {type: array, items: {type: integer, enum: [2]}} + second_value: {type: string} + ContainerChoice: + anyOf: + - {$ref: '#/components/schemas/ContainerOne'} + - {$ref: '#/components/schemas/ContainerTwo'} + Alpha: + type: object + required: [wire_name] + properties: + wire_name: {type: string} + first_value: {type: string} + Beta: + type: object + required: [wire_name] + properties: + wire_name: {type: string} + second_value: {type: string} + Ambiguous: + anyOf: + - {$ref: '#/components/schemas/Alpha'} + - {$ref: '#/components/schemas/Beta'} + AmbiguousAlias: {$ref: '#/components/schemas/Ambiguous'} + OpenNested: + anyOf: + - {$ref: '#/components/schemas/AmbiguousAlias'} + - {} + OpaqueFirst: + anyOf: + - {} + - {$ref: '#/components/schemas/FiniteTwo'} + LargeOne: + type: object + required: [revision] + properties: + revision: {type: integer, format: int64, enum: [9007199254740992]} + LargeTwo: + type: object + required: [revision] + properties: + revision: {type: integer, format: int64, enum: [9007199254740993]} + second_value: {type: string} + LargeChoice: + oneOf: + - {$ref: '#/components/schemas/LargeOne'} + - {$ref: '#/components/schemas/LargeTwo'} + ExcludedNull: + type: object + required: [revision] + properties: + revision: + anyOf: [{type: integer, enum: [1]}, {type: 'null'}] + enum: [1] + IncludedNull: + type: object + required: [revision] + properties: + revision: {type: [integer, 'null'], enum: [2, null]} + second_value: {type: string} + NullableFields: + oneOf: + - {$ref: '#/components/schemas/ExcludedNull'} + - {$ref: '#/components/schemas/IncludedNull'} + NullableChoice: + anyOf: + - {$ref: '#/components/schemas/Alpha'} + - {type: 'null'} + BranchOne: + type: object + required: [revision, children] + properties: + children: {type: array, items: {$ref: '#/components/schemas/Tree'}} + revision: {type: integer, enum: [1]} + BranchTwo: + type: object + required: [revision, children] + properties: + children: {type: array, items: {$ref: '#/components/schemas/Tree'}} + revision: {type: integer, enum: [2]} + second_value: {type: string} + Tree: + oneOf: + - {$ref: '#/components/schemas/BranchOne'} + - {$ref: '#/components/schemas/BranchTwo'} + ScalarChoice: + oneOf: + - {type: integer, enum: [1]} + - {type: number, enum: [2.5]} + - {type: boolean, enum: [true]} + BinaryChoice: + oneOf: + - {type: string, format: binary} + - {type: string} + Narrow: + type: object + required: [identifier] + properties: + identifier: {type: integer, format: int32} + Wide: + type: object + required: [identifier] + properties: + identifier: {type: integer, format: int64} + second_value: {type: string} + FormatChoice: + anyOf: + - {$ref: '#/components/schemas/Narrow'} + - {$ref: '#/components/schemas/Wide'} + FormatFallback: + anyOf: + - {$ref: '#/components/schemas/Narrow'} + - {} + NumberAlias: {type: integer} + AliasChoice: + anyOf: + - {$ref: '#/components/schemas/NumberAlias'} + - {$ref: '#/components/schemas/Alpha'} + ChoiceEnvelope: + type: object + required: [caption, choice, choices, by_name] + properties: + caption: {type: string} + choice: {$ref: '#/components/schemas/FiniteChoice'} + choices: {type: array, items: {$ref: '#/components/schemas/FiniteChoice'}} + by_name: {type: object, additionalProperties: {$ref: '#/components/schemas/FiniteChoice'}} + ordinary_null: {type: string} + LeftPart: + type: object + required: [left_value] + properties: + left_value: {type: string} + RightPart: + type: object + required: [right_value] + properties: + right_value: {type: integer, enum: [2]} + WrappedPair: + allOf: + - {$ref: '#/components/schemas/LeftPart'} + - {$ref: '#/components/schemas/RightPart'} + WrappedChoice: + anyOf: + - {$ref: '#/components/schemas/WrappedPair'} + - {$ref: '#/components/schemas/Alpha'} + VariantRaw: + anyOf: + - {$ref: '#/components/schemas/Alpha'} + - {$ref: '#/components/schemas/Beta'} + recursive_choice: + anyOf: + - {$ref: '#/components/schemas/recursive_choice'} + - {type: string} + NumericZero: + anyOf: + - {type: number, enum: [0]} + - {} + NumericFinite: + anyOf: + - {type: number, enum: [1.25]} + - {} + IntegerFallback: + anyOf: + - {type: integer} + - {} diff --git a/tests/generated-clients/jvm/README.md b/tests/generated-clients/jvm/README.md index d3463134b..c3b65d738 100644 --- a/tests/generated-clients/jvm/README.md +++ b/tests/generated-clients/jvm/README.md @@ -2,6 +2,14 @@ Java and Kotlin each have an independent Gradle consumer build. The public CLI generates the six inputs in `fixtures.txt` into ignored `generated/` directories. +Each backend can add independent cases through its own `java/fixtures.txt` or +`kotlin/fixtures.txt`; the same manifests drive generation and Gradle discovery. +An optional third column selects `firstMatch`, `strict`, or `raw`. Java's union +consumers cover finite constraints, recursive checks, wrapped alias/composition +boundaries, raw null preservation, and OkHttp response/error-body integration. +Independent helper-type projects also compile recursive unions alongside models +named `Integer` and `Boolean`; numeric controls exercise extreme exponents without +weakening native delegate errors or finite-value precision. Generated manifests and source are used unchanged. Each fixture has a separate consumer subproject, so identically named SDK classes do not share a classpath. diff --git a/tests/generated-clients/jvm/java/cases/union-first-match/build.gradle b/tests/generated-clients/jvm/java/cases/union-first-match/build.gradle new file mode 100644 index 000000000..e76eca30a --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-first-match/build.gradle @@ -0,0 +1,2 @@ +sourceSets.test.java.srcDir("../../support/union-policies") +sourceSets.test.java.srcDir("../../support/http") diff --git a/tests/generated-clients/jvm/java/cases/union-first-match/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-first-match/gradle.lockfile new file mode 100644 index 000000000..5ec960c1c --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-first-match/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-first-match:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-first-match/src/test/java/UnionPolicyTest.java b/tests/generated-clients/jvm/java/cases/union-first-match/src/test/java/UnionPolicyTest.java new file mode 100644 index 000000000..0727293c7 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-first-match/src/test/java/UnionPolicyTest.java @@ -0,0 +1,3 @@ +class UnionPolicyTest extends TypedUnionPolicyCases { + @Override protected boolean strict() { return false; } +} diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types-strict/build.gradle b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/build.gradle new file mode 100644 index 000000000..aadd1c02d --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/build.gradle @@ -0,0 +1 @@ +sourceSets.test.java.srcDir("../../support/helper-types") diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types-strict/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/gradle.lockfile new file mode 100644 index 000000000..99bba2153 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-helper-types-strict:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types-strict/src/test/java/HelperTypesTest.java b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/src/test/java/HelperTypesTest.java new file mode 100644 index 000000000..368f25b06 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types-strict/src/test/java/HelperTypesTest.java @@ -0,0 +1 @@ +class HelperTypesTest extends RecursiveHelperTypeCases {} diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types/build.gradle b/tests/generated-clients/jvm/java/cases/union-helper-types/build.gradle new file mode 100644 index 000000000..aadd1c02d --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types/build.gradle @@ -0,0 +1 @@ +sourceSets.test.java.srcDir("../../support/helper-types") diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-helper-types/gradle.lockfile new file mode 100644 index 000000000..cbde47ccc --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-helper-types:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-helper-types/src/test/java/HelperTypesTest.java b/tests/generated-clients/jvm/java/cases/union-helper-types/src/test/java/HelperTypesTest.java new file mode 100644 index 000000000..368f25b06 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-helper-types/src/test/java/HelperTypesTest.java @@ -0,0 +1 @@ +class HelperTypesTest extends RecursiveHelperTypeCases {} diff --git a/tests/generated-clients/jvm/java/cases/union-raw/build.gradle b/tests/generated-clients/jvm/java/cases/union-raw/build.gradle new file mode 100644 index 000000000..e5074adee --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-raw/build.gradle @@ -0,0 +1 @@ +sourceSets.test.java.srcDir("../../support/http") diff --git a/tests/generated-clients/jvm/java/cases/union-raw/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-raw/gradle.lockfile new file mode 100644 index 000000000..8b21996c1 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-raw/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-raw:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-raw/src/test/java/RawUnionPolicyTest.java b/tests/generated-clients/jvm/java/cases/union-raw/src/test/java/RawUnionPolicyTest.java new file mode 100644 index 000000000..90e13cff2 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-raw/src/test/java/RawUnionPolicyTest.java @@ -0,0 +1,74 @@ +import com.example.sdk.models.*; +import com.example.sdk.apis.*; +import com.google.gson.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class RawUnionPolicyTest { + private final Gson gson = new Gson(); + + @Test void rawPreservesKnownAmbiguousUnmatchedAndNullJsonIncludingNullEntries() { + for (String wire : new String[] { + "null", "42", "{\"revision\":2,\"unknown\":null}", + "{\"wire_name\":\"detail\",\"second_value\":\"two\"}", + "{\"unknown\":[null,true,{\"nested_null\":null}]}" + }) { + FiniteChoice.VariantRaw value = assertInstanceOf(FiniteChoice.VariantRaw.class, gson.fromJson(wire, FiniteChoice.class)); + JsonElement expected = JsonParser.parseString(wire); + assertEquals(expected, value.value0()); + assertEquals(expected, gson.toJsonTree(value, FiniteChoice.class)); + assertEquals(expected, JsonParser.parseString(gson.toJson(value, FiniteChoice.class))); + } + } + + @Test void rawWrapperCanAlsoWriteAJavaNullPayload() { + assertEquals(JsonNull.INSTANCE, gson.toJsonTree(new FiniteChoice.VariantRaw(null), FiniteChoice.class)); + } + + @Test void rawCaseNamesDoNotCollideWithTheirEnclosingInterface() { + String wire = "{\"nullable\":null}"; + VariantRaw.VariantRawVariant value = assertInstanceOf(VariantRaw.VariantRawVariant.class, gson.fromJson(wire, VariantRaw.class)); + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(value, VariantRaw.class)); + } + + @Test void rawUnionSubtreesDoNotEraseOrdinaryContainingModelFields() { + String wire = "{\"caption\":\"envelope\",\"choice\":{\"nullable\":null},\"choices\":[42],\"by_name\":{\"keep_key\":false}}"; + ChoiceEnvelope value = gson.fromJson(wire, ChoiceEnvelope.class); + assertEquals("envelope", value.getCaption()); + assertInstanceOf(FiniteChoice.VariantRaw.class, value.getChoice()); + assertInstanceOf(FiniteChoice.VariantRaw.class, value.getChoices().get(0)); + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(value)); + } + + @Test void rawWritingRestoresTheSurroundingGsonNullPolicy() { + String wire = "{\"caption\":\"envelope\",\"choice\":{\"nullable\":null},\"choices\":[],\"by_name\":{},\"ordinary_null\":null}"; + ChoiceEnvelope value = gson.fromJson(wire, ChoiceEnvelope.class); + JsonObject expected = JsonParser.parseString(wire).getAsJsonObject(); + expected.remove("ordinary_null"); + assertEquals(expected, gson.toJsonTree(value)); + assertEquals(expected, JsonParser.parseString(gson.toJson(value))); + } + + @Test void rawLargeNumbersNeverPassThroughDoublePrecision() { + String wire = "{\"big\":18446744073709551615,\"nullable\":null}"; + FiniteChoice value = gson.fromJson(wire, FiniteChoice.class); + JsonObject tree = gson.toJsonTree(value, FiniteChoice.class).getAsJsonObject(); + assertEquals("18446744073709551615", tree.get("big").getAsString()); + assertTrue(gson.toJson(value, FiniteChoice.class).contains("18446744073709551615")); + assertTrue(tree.has("nullable")); + } + + @Test void rawHttpSuccessPreservesUnmatchedPayloads() throws Exception { + String wire = "{\"unmatched\":true,\"nullable\":null}"; + FiniteChoice.VariantRaw value = assertInstanceOf(FiniteChoice.VariantRaw.class, UnionHttpTestSupport.api(200, wire).getChoice().getStatus200()); + assertEquals(JsonParser.parseString(wire), value.value0()); + } + + @Test void rawHttpErrorBodiesPreserveUnmatchedPayloads() { + String wire = "{\"unmatched\":true,\"nullable\":null}"; + GetChoiceException error = assertThrows(GetChoiceException.class, () -> UnionHttpTestSupport.api(400, wire).getChoice()); + GetChoiceBadRequest detail = assertInstanceOf(GetChoiceBadRequest.class, error.detail()); + FiniteChoice.VariantRaw value = assertInstanceOf(FiniteChoice.VariantRaw.class, detail.body()); + assertEquals(JsonParser.parseString(wire), value.value0()); + } +} diff --git a/tests/generated-clients/jvm/java/cases/union-selection/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-selection/gradle.lockfile new file mode 100644 index 000000000..0b04d2594 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-selection/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-selection:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-selection/src/test/java/UnionSelectionTest.java b/tests/generated-clients/jvm/java/cases/union-selection/src/test/java/UnionSelectionTest.java new file mode 100644 index 000000000..46afe71b4 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-selection/src/test/java/UnionSelectionTest.java @@ -0,0 +1,62 @@ +import com.example.sdk.models.*; +import com.google.gson.Gson; +import com.google.gson.JsonParser; +import java.util.List; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class UnionSelectionTest { + private final Gson gson = new Gson(); + private static final String CLIENT = "{\"name\":\"weather\",\"input_schema\":{\"type\":\"object\"}}"; + private static final String SEARCH = "{\"type\":\"web_search_20250305\",\"name\":\"web_search\",\"max_uses\":1}"; + private static final String NUMERIC = "{\"revision\":2,\"second_value\":\"two\"}"; + + private void roundTrip(String wire, Class type) { + T value = gson.fromJson(wire, type); + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(value, type)); + } + + @Test void directToolMembersPreserveWire() { + roundTrip(CLIENT, ClientTool.class); + roundTrip(SEARCH, SearchTool.class); + } + + @Test void typedUnionRequestIsJsonSerializable() { + Request request = new Request(List.of( + new Tool.VariantClientTool(gson.fromJson(CLIENT, ClientTool.class)), + new Tool.VariantSearchTool(gson.fromJson(SEARCH, SearchTool.class)) + )); + assertEquals(JsonParser.parseString("{\"tools\":[" + CLIENT + "," + SEARCH + "]}"), gson.toJsonTree(request)); + } + + @Test void inboundToolRequestConstructsUnionVariants() { + String wire = "{\"tools\":[" + CLIENT + "," + SEARCH + "]}"; + Request request = gson.fromJson(wire, Request.class); + assertInstanceOf(Tool.VariantClientTool.class, request.getTools().get(0)); + assertInstanceOf(Tool.VariantSearchTool.class, request.getTools().get(1)); + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(request)); + } + + @Test void directNumericMemberPreservesPayload() { roundTrip(NUMERIC, NumericTwo.class); } + + @Test void disjointRequiredFieldControlSelectsSecondMember() { + RequiredChoice value = gson.fromJson(NUMERIC, RequiredChoice.class); + assertInstanceOf(RequiredChoice.VariantRequiredTwo.class, value); + roundTrip(NUMERIC, RequiredChoice.class); + } + + @Test void disjointEnumConstraintSelectsSecondMember() { + Choice value = gson.fromJson(NUMERIC, Choice.class); + assertInstanceOf(Choice.VariantNumericTwo.class, value); + roundTrip(NUMERIC, Choice.class); + } + + @Test void nestedUnionSelectionPreservesScalarArrayAndMapPayloads() { + String wire = "{\"choice\":" + NUMERIC + ",\"choice_list\":[" + NUMERIC + "],\"choice_map\":{\"keep_key\":" + NUMERIC + "}}"; + Envelope value = gson.fromJson(wire, Envelope.class); + assertInstanceOf(Choice.VariantNumericTwo.class, value.getChoice()); + assertInstanceOf(Choice.VariantNumericTwo.class, value.getChoiceList().get(0)); + assertInstanceOf(Choice.VariantNumericTwo.class, value.getChoiceMap().get("keep_key")); + roundTrip(wire, Envelope.class); + } +} diff --git a/tests/generated-clients/jvm/java/cases/union-strict/build.gradle b/tests/generated-clients/jvm/java/cases/union-strict/build.gradle new file mode 100644 index 000000000..e76eca30a --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-strict/build.gradle @@ -0,0 +1,2 @@ +sourceSets.test.java.srcDir("../../support/union-policies") +sourceSets.test.java.srcDir("../../support/http") diff --git a/tests/generated-clients/jvm/java/cases/union-strict/gradle.lockfile b/tests/generated-clients/jvm/java/cases/union-strict/gradle.lockfile new file mode 100644 index 000000000..f88d4a1b8 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-strict/gradle.lockfile @@ -0,0 +1,22 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +# To regenerate this file, run: ./gradlew :cases:union-strict:dependencies --write-locks +com.google.code.gson:gson:2.11.0=testCompileClasspath +com.google.errorprone:error_prone_annotations:2.27.0=testCompileClasspath +com.squareup.okhttp3:okhttp:4.12.0=testCompileClasspath +com.squareup.okio:okio-jvm:3.6.0=testCompileClasspath +com.squareup.okio:okio:3.6.0=testCompileClasspath +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=testCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.10=testCompileClasspath +org.jetbrains:annotations:13.0=testCompileClasspath +org.junit.jupiter:junit-jupiter-api:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter-params:5.12.2=testCompileClasspath +org.junit.jupiter:junit-jupiter:5.12.2=testCompileClasspath +org.junit.platform:junit-platform-commons:1.12.2=testCompileClasspath +org.junit:junit-bom:5.12.2=testCompileClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath +empty=testAnnotationProcessor diff --git a/tests/generated-clients/jvm/java/cases/union-strict/src/test/java/UnionPolicyTest.java b/tests/generated-clients/jvm/java/cases/union-strict/src/test/java/UnionPolicyTest.java new file mode 100644 index 000000000..95e13f0d3 --- /dev/null +++ b/tests/generated-clients/jvm/java/cases/union-strict/src/test/java/UnionPolicyTest.java @@ -0,0 +1,3 @@ +class UnionPolicyTest extends TypedUnionPolicyCases { + @Override protected boolean strict() { return true; } +} diff --git a/tests/generated-clients/jvm/java/fixtures.txt b/tests/generated-clients/jvm/java/fixtures.txt new file mode 100644 index 000000000..c319f39b7 --- /dev/null +++ b/tests/generated-clients/jvm/java/fixtures.txt @@ -0,0 +1,7 @@ +# Additional Java-only consumers; shared fixtures remain in ../fixtures.txt. +union-selection valid/union-selection.yaml +union-first-match valid/java-union-policies.yaml firstMatch +union-strict valid/java-union-policies.yaml strict +union-raw valid/java-union-policies.yaml raw +union-helper-types valid/java-union-helper-types.yaml firstMatch +union-helper-types-strict valid/java-union-helper-types.yaml strict diff --git a/tests/generated-clients/jvm/java/support/helper-types/RecursiveHelperTypeCases.java b/tests/generated-clients/jvm/java/support/helper-types/RecursiveHelperTypeCases.java new file mode 100644 index 000000000..3b551006e --- /dev/null +++ b/tests/generated-clients/jvm/java/support/helper-types/RecursiveHelperTypeCases.java @@ -0,0 +1,17 @@ +import com.example.sdk.models.*; +import com.google.gson.Gson; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +abstract class RecursiveHelperTypeCases { + @Test void recursiveCachesDoNotResolveBoxedTypesToGeneratedModels() { + Gson gson = new Gson(); + String wire = "{\"children\":[{\"children\":[\"leaf\"]}]}"; + Tree.VariantBranch value = assertInstanceOf(Tree.VariantBranch.class, gson.fromJson(wire, Tree.class)); + assertInstanceOf(Tree.VariantBranch.class, value.value0().getChildren().get(0)); + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(value, Tree.class)); + assertEquals("boolean-model", new com.example.sdk.models.Boolean("boolean-model").getValue()); + assertEquals("integer-model", new com.example.sdk.models.Integer("integer-model").getValue()); + } +} diff --git a/tests/generated-clients/jvm/java/support/http/UnionHttpTestSupport.java b/tests/generated-clients/jvm/java/support/http/UnionHttpTestSupport.java new file mode 100644 index 000000000..22013c432 --- /dev/null +++ b/tests/generated-clients/jvm/java/support/http/UnionHttpTestSupport.java @@ -0,0 +1,18 @@ +import com.example.sdk.apis.SelectionApi; +import com.example.sdk.runtime.ApiClient; +import java.util.Map; +import okhttp3.*; +import static org.junit.jupiter.api.Assertions.*; + +final class UnionHttpTestSupport { + static SelectionApi api(int status, String wire) { + OkHttpClient transport = new OkHttpClient.Builder().addInterceptor(chain -> { + assertEquals("/choice", chain.request().url().encodedPath()); + return new Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1) + .code(status).message(status == 200 ? "OK" : "Bad Request") + .header("Content-Type", "application/json") + .body(ResponseBody.create(wire, MediaType.get("application/json"))).build(); + }).build(); + return new SelectionApi(new ApiClient("https://union.test", transport, null, Map.of())); + } +} diff --git a/tests/generated-clients/jvm/java/support/union-policies/TypedUnionPolicyCases.java b/tests/generated-clients/jvm/java/support/union-policies/TypedUnionPolicyCases.java new file mode 100644 index 000000000..563200a18 --- /dev/null +++ b/tests/generated-clients/jvm/java/support/union-policies/TypedUnionPolicyCases.java @@ -0,0 +1,244 @@ +import com.example.sdk.models.*; +import com.example.sdk.apis.*; +import com.google.gson.*; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +abstract class TypedUnionPolicyCases { + protected abstract boolean strict(); + private final Gson gson = new Gson(); + private static final String FINITE = "{\"revision\":2,\"values\":[2,2.5],\"flags\":{\"keep_key\":true},\"second_value\":\"two\"}"; + + private T read(String wire, Class type) { + return gson.fromJson(wire, type); + } + + private void roundTrip(String wire, Class type) { + assertEquals(JsonParser.parseString(wire), gson.toJsonTree(read(wire, type), type)); + } + + @Test void finiteScalarArrayAndMapFactsSelectTheSecondMember() { + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, read(FINITE, FiniteChoice.class)); + roundTrip(FINITE, FiniteChoice.class); + } + + @Test void unmatchedValuesAndCoercedScalarKindsAreRejected() { + for (String wire : new String[] { + FINITE.replace("\"revision\":2", "\"revision\":3"), + FINITE.replace("\"revision\":2", "\"revision\":\"2\""), + FINITE.replace("[2,2.5]", "[1.5]"), + FINITE.replace("true", "false"), + "{\"revision\":2}", "42", "[]" + }) { + assertThrows(JsonParseException.class, () -> read(wire, FiniteChoice.class), wire); + } + } + + @Test void containerFactsDistinguishMembersWithoutAScalarTag() { + String wire = "{\"values\":[2,2],\"second_value\":\"two\"}"; + assertInstanceOf(ContainerChoice.VariantContainerTwo.class, read(wire, ContainerChoice.class)); + roundTrip(wire, ContainerChoice.class); + assertThrows(JsonParseException.class, () -> read("{\"values\":[3]}", ContainerChoice.class)); + } + + @Test void largeIntegersRemainDisjointAboveDoublePrecision() { + String wire = "{\"revision\":9007199254740993,\"second_value\":\"two\"}"; + LargeChoice.VariantLargeTwo value = assertInstanceOf(LargeChoice.VariantLargeTwo.class, read(wire, LargeChoice.class)); + assertEquals(9007199254740993L, value.value0().getRevision()); + roundTrip(wire, LargeChoice.class); + } + + @Test void zeroExponentsDoNotIntroduceMatcherScaleLimits() { + for (String wire : new String[] {"0e10000", "0e-10000", "-0.000e10000", "0e2147483648", "0e-2147483649"}) { + double ordinary = gson.fromJson(JsonParser.parseString(wire), Double.class); + NumericZero.VariantNumber value = assertInstanceOf(NumericZero.VariantNumber.class, read(wire, NumericZero.class)); + assertEquals(ordinary, value.value0(), wire); + } + } + + @Test void unmatchedExponentsReachOpaqueFallbackWithoutRounding() { + for (String wire : new String[] {"1e10000", "1e-10000", "1e2147483648", "1e-2147483649", "12500000000000000001e-19"}) { + NumericFinite.VariantAny value = assertInstanceOf(NumericFinite.VariantAny.class, read(wire, NumericFinite.class)); + assertEquals(wire, value.value0().getAsString()); + } + for (String wire : new String[] {"1.25", "125e-2", "0.0125000e2", "12500000000000000000e-19"}) { + assertInstanceOf(NumericFinite.VariantNumber.class, read(wire, NumericFinite.class)); + } + } + + @Test void integerSelectionDoesNotHideTheNativeDelegateLimit() { + String wire = "0e10000"; + JsonParseException ordinary = assertThrows(JsonParseException.class, () -> gson.fromJson(JsonParser.parseString(wire), Long.class)); + JsonParseException selected = assertThrows(JsonParseException.class, () -> read(wire, IntegerFallback.class)); + assertEquals(ordinary.getMessage(), selected.getMessage()); + for (String fractional : new String[] {"1e-10000", "1e-2147483649"}) { + assertInstanceOf(IntegerFallback.VariantAny.class, read(fractional, IntegerFallback.class)); + } + } + + @Test void binarySelectionRejectsOutOfRangeExponentsWithoutParserFailures() { + for (String wire : new String[] {"[1e10000]", "[-1e10000]", "[1e-10000]", "[1e2147483648]"}) { + JsonParseException error = assertThrows(JsonParseException.class, () -> read(wire, BinaryChoice.class)); + assertTrue(error.getMessage().contains("unknown BinaryChoice union value")); + } + String wire = "[0e10000]"; + JsonParseException ordinary = assertThrows(JsonParseException.class, () -> gson.fromJson(JsonParser.parseString(wire), byte[].class)); + JsonParseException selected = assertThrows(JsonParseException.class, () -> read(wire, BinaryChoice.class)); + assertEquals(ordinary.getMessage(), selected.getMessage()); + } + + @Test void scalarFiniteFactsDoNotWidenTheirJsonKinds() { + assertInstanceOf(ScalarChoice.VariantInteger.class, read("1", ScalarChoice.class)); + assertInstanceOf(ScalarChoice.VariantNumber.class, read("2.5", ScalarChoice.class)); + assertInstanceOf(ScalarChoice.VariantBoolean.class, read("true", ScalarChoice.class)); + for (String wire : new String[] {"2", "false", "\"1\""}) { + assertThrows(JsonParseException.class, () -> read(wire, ScalarChoice.class)); + } + } + + @Test void finiteNullPermissionsStayAtTheirAuthoredBoundary() { + assertInstanceOf(NullableFields.VariantIncludedNull.class, read("{\"revision\":null,\"second_value\":\"two\"}", NullableFields.class)); + assertInstanceOf(NullableFields.VariantExcludedNull.class, read("{\"revision\":1}", NullableFields.class)); + } + + @Test void typedRootNullHandlingRetainsTheExistingJavaApi() { + assertNull(read("null", FiniteChoice.class)); + assertSame(NullableChoice.VariantNull.INSTANCE, read("null", NullableChoice.class)); + roundTrip("null", NullableChoice.class); + } + + @Test void concreteMembersPrecedeOpaqueFallbacks() { + assertInstanceOf(OpaqueFirst.VariantFiniteTwo.class, read(FINITE, OpaqueFirst.class)); + roundTrip(FINITE, OpaqueFirst.class); + assertInstanceOf(OpaqueFirst.VariantAny.class, read("42", OpaqueFirst.class)); + roundTrip("42", OpaqueFirst.class); + } + + @Test void ambiguityIsCountedBeforeTheSelectedDelegateRuns() { + String wire = "{\"wire_name\":\"detail\",\"first_value\":\"one\"}"; + if (strict()) { + JsonParseException error = assertThrows(JsonParseException.class, () -> read(wire, Ambiguous.class)); + assertTrue(error.getMessage().contains("ambiguous untagged union Ambiguous")); + } else { + assertInstanceOf(Ambiguous.VariantAlpha.class, read(wire, Ambiguous.class)); + roundTrip(wire, Ambiguous.class); + } + } + + @Test void aliasWrappersRetainTheirOwnConversionBoundary() { + String wire = "{\"value\":7}"; + AliasChoice.VariantNumberAlias value = assertInstanceOf(AliasChoice.VariantNumberAlias.class, read(wire, AliasChoice.class)); + assertEquals(7L, value.value0().getValue()); + roundTrip(wire, AliasChoice.class); + assertInstanceOf(AliasChoice.VariantAlpha.class, read("{\"wire_name\":\"detail\"}", AliasChoice.class)); + } + + @Test void nestedStrictDelegateErrorsDoNotFallBackToOpaqueJson() { + String wire = "{\"value\":{\"wire_name\":\"detail\"}}"; + if (strict()) { + JsonParseException error = assertThrows(JsonParseException.class, () -> read(wire, OpenNested.class)); + assertTrue(error.getMessage().contains("ambiguous untagged union Ambiguous")); + } else { + assertInstanceOf(OpenNested.VariantAmbiguousAlias.class, read(wire, OpenNested.class)); + roundTrip(wire, OpenNested.class); + } + } + + @Test void intersectionMembersKeepTheirExistingWrappedWireRepresentation() { + String wire = "{\"leftPart\":{\"left_value\":\"left\"},\"rightPart\":{\"right_value\":2}}"; + assertInstanceOf(WrappedChoice.VariantWrappedPair.class, read(wire, WrappedChoice.class)); + roundTrip(wire, WrappedChoice.class); + assertInstanceOf(WrappedChoice.VariantAlpha.class, read("{\"wire_name\":\"detail\"}", WrappedChoice.class)); + } + + @Test void deepRecursivePredicatesReuseBothSuccessfulAndFailedChecks() { + JsonObject wire = JsonParser.parseString("{\"revision\":2,\"children\":[],\"second_value\":\"leaf\"}").getAsJsonObject(); + JsonObject invalid = JsonParser.parseString("{\"revision\":3,\"children\":[]}").getAsJsonObject(); + for (int i = 0; i < 24; i++) { + JsonObject parent = new JsonObject(); + JsonArray children = new JsonArray(); + children.add(wire); + parent.add("children", children); + parent.addProperty("revision", 2); + parent.addProperty("second_value", "branch"); + wire = parent; + JsonObject badParent = new JsonObject(); + JsonArray badChildren = new JsonArray(); + badChildren.add(invalid); + badParent.add("children", badChildren); + badParent.addProperty("revision", 2); + invalid = badParent; + } + assertInstanceOf(Tree.VariantBranchTwo.class, gson.fromJson(wire, Tree.class)); + roundTrip(wire.toString(), Tree.class); + JsonObject rejected = invalid; + assertThrows(JsonParseException.class, () -> gson.fromJson(rejected, Tree.class)); + } + + @Test void recursiveSchemaIdentityIsNotItsPascalCaseJavaName() { + assertInstanceOf(RecursiveChoice.VariantString.class, read("\"safe\"", RecursiveChoice.class)); + roundTrip("\"safe\"", RecursiveChoice.class); + } + + @Test void binaryMembersMatchGsonsNativeByteArrayRepresentation() { + assertInstanceOf(BinaryChoice.VariantBinary.class, read("[-128,0,127]", BinaryChoice.class)); + roundTrip("[-128,0,127]", BinaryChoice.class); + assertInstanceOf(BinaryChoice.VariantString.class, read("\"text\"", BinaryChoice.class)); + assertThrows(JsonParseException.class, () -> read("[256]", BinaryChoice.class)); + } + + @Test void nativeFormatBoundsDoNotBecomeSelectionValidators() { + String wire = "{\"identifier\":2147483648,\"second_value\":\"two\"}"; + // The existing adapter delegates through Gson's JsonElement path, + // whose integer overflow behavior differs from its streaming reader. + int nativeValue = gson.fromJson(JsonParser.parseString(wire), Narrow.class).getIdentifier(); + if (strict()) { + JsonParseException error = assertThrows(JsonParseException.class, () -> read(wire, FormatChoice.class)); + assertTrue(error.getMessage().contains("ambiguous untagged union FormatChoice")); + } else { + FormatChoice.VariantNarrow value = assertInstanceOf(FormatChoice.VariantNarrow.class, read(wire, FormatChoice.class)); + assertEquals(nativeValue, value.value0().getIdentifier()); + } + FormatFallback.VariantNarrow value = assertInstanceOf(FormatFallback.VariantNarrow.class, read(wire, FormatFallback.class)); + assertEquals(nativeValue, value.value0().getIdentifier()); + } + + @Test void containingModelsUseTheSameSelectionInFieldsListsAndMaps() { + String wire = "{\"caption\":\"envelope\",\"choice\":" + FINITE + ",\"choices\":[" + FINITE + "],\"by_name\":{\"keep_key\":" + FINITE + "}}"; + ChoiceEnvelope value = read(wire, ChoiceEnvelope.class); + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, value.getChoice()); + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, value.getChoices().get(0)); + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, value.getByName().get("keep_key")); + roundTrip(wire, ChoiceEnvelope.class); + } + + @Test void successfulHttpResponsesUseTheSelectedTypedMember() throws Exception { + GetChoiceResponse response = UnionHttpTestSupport.api(200, FINITE).getChoice(); + assertEquals(200, response.getStatusCode()); + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, response.getStatus200()); + assertEquals(JsonParser.parseString(FINITE), gson.toJsonTree(response.getStatus200(), FiniteChoice.class)); + } + + @Test void typedHttpErrorsPreservePayloadsAndRawBytes() { + GetChoiceException error = assertThrows(GetChoiceException.class, () -> UnionHttpTestSupport.api(400, FINITE).getChoice()); + GetChoiceBadRequest detail = assertInstanceOf(GetChoiceBadRequest.class, error.detail()); + assertInstanceOf(FiniteChoice.VariantFiniteTwo.class, detail.body()); + assertArrayEquals(FINITE.getBytes(StandardCharsets.UTF_8), error.rawBody()); + assertArrayEquals(error.rawBody(), detail.rawBody()); + } + + @Test void unmatchedHttpSuccessBodiesPropagateSelectionErrors() { + String wire = FINITE.replace("\"revision\":2", "\"revision\":3"); + assertThrows(JsonParseException.class, () -> UnionHttpTestSupport.api(200, wire).getChoice()); + } + + @Test void unmatchedHttpErrorBodiesRemainLazyAndKeepTheirDiagnostics() { + String wire = FINITE.replace("\"revision\":2", "\"revision\":3"); + GetChoiceException error = assertThrows(GetChoiceException.class, () -> UnionHttpTestSupport.api(400, wire).getChoice()); + GetChoiceBadRequest detail = assertInstanceOf(GetChoiceBadRequest.class, error.detail()); + assertThrows(JsonParseException.class, detail::body); + assertThrows(JsonParseException.class, detail::body); + assertArrayEquals(wire.getBytes(StandardCharsets.UTF_8), error.rawBody()); + } +} diff --git a/tests/generated-clients/jvm/settings-common.gradle b/tests/generated-clients/jvm/settings-common.gradle index 51ceaeba9..917246ff5 100644 --- a/tests/generated-clients/jvm/settings-common.gradle +++ b/tests/generated-clients/jvm/settings-common.gradle @@ -1,11 +1,18 @@ rootProject.name = "${settingsDir.name}-client-tests" include(":clients") project(":clients").projectDir = new File(settingsDir, "generated") -new File(settingsDir, "../fixtures.txt").eachLine { line -> - if (line.trim() && !line.startsWith("#")) { - def name = line.tokenize()[0] - include(":clients:${name}", ":cases:${name}") - project(":clients:${name}").projectDir = new File(settingsDir, "generated/${name}") - project(":cases:${name}").projectDir = new File(settingsDir, "cases/${name}") +def manifests = [new File(settingsDir, "../fixtures.txt")] +def backendManifest = new File(settingsDir, "fixtures.txt") +if (backendManifest.isFile()) { + manifests.add(backendManifest) +} +manifests.each { manifest -> + manifest.eachLine { line -> + if (line.trim() && !line.startsWith("#")) { + def name = line.tokenize()[0] + include(":clients:${name}", ":cases:${name}") + project(":clients:${name}").projectDir = new File(settingsDir, "generated/${name}") + project(":cases:${name}").projectDir = new File(settingsDir, "cases/${name}") + } } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden index 7fa87afe6..adcf27387 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/AnyOfTypes.java.golden @@ -31,15 +31,22 @@ final class AnyOfTypesJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public AnyOfTypes read (JsonReader reader) throws java.io.IOException { + public AnyOfTypes read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected == 0) { return new AnyOfTypes.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 1) { return new AnyOfTypes.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown AnyOfTypes union value"); @@ -60,4 +67,12 @@ final class AnyOfTypesJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden index 3ae650f22..396f9d5fd 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfObjects.java.golden @@ -31,15 +31,22 @@ final class OneOfObjectsJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public OneOfObjects read (JsonReader reader) throws java.io.IOException { + public OneOfObjects read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches4(element)) { + selected = 1; + } + if (selected == 0) { return new OneOfObjects.VariantBasicObject(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonObject()) { + if (selected == 1) { return new OneOfObjects.VariantNestedObject(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown OneOfObjects union value"); @@ -60,4 +67,87 @@ final class OneOfObjectsJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("active") && !matches1(object.get("active"))) { + return false; + } + if (!object.has("id") || !matches2(object.get("id"))) { + return false; + } + if (!object.has("name") || !matches3(object.get("name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("metadata") && !matches5(object.get("metadata"))) { + return false; + } + if (object.has("user") && !matches6(object.get("user"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("created") && !matches3(object.get("created"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("id") || !matches2(object.get("id"))) { + return false; + } + if (object.has("name") && !matches3(object.get("name"))) { + return false; + } + return true; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden index 6b368d8e9..14f201b46 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfTypes.java.golden @@ -33,18 +33,28 @@ final class OneOfTypesJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public OneOfTypes read (JsonReader reader) throws java.io.IOException { + public OneOfTypes read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected < 0 && matches2(element)) { + selected = 2; + } + if (selected == 0) { return new OneOfTypes.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 1) { return new OneOfTypes.VariantNumber(gson.fromJson(element, Double.class)); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) { + if (selected == 2) { return new OneOfTypes.VariantBoolean(element.getAsBoolean()); } throw new JsonParseException("unknown OneOfTypes union value"); @@ -69,4 +79,16 @@ final class OneOfTypesJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } } diff --git a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden index 3c05f4aed..3802c2176 100644 --- a/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden +++ b/tests/golden/java/java-okhttp/comprehensive-schemas/models/OneOfWithNull.java.golden @@ -36,15 +36,22 @@ final class OneOfWithNullJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public OneOfWithNull read (JsonReader reader) throws java.io.IOException { + public OneOfWithNull read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return OneOfWithNull.VariantNull.INSTANCE; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected == 0) { return new OneOfWithNull.VariantString(element.getAsString()); } - if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + if (selected == 1) { return new OneOfWithNull.VariantBasicObject(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown OneOfWithNull union value"); @@ -69,4 +76,48 @@ final class OneOfWithNullJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("active") && !matches2(object.get("active"))) { + return false; + } + if (!object.has("id") || !matches3(object.get("id"))) { + return false; + } + if (!object.has("name") || !matches0(object.get("name"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/enum-repr/models/MixedEnum.java.golden b/tests/golden/java/java-okhttp/enum-repr/models/MixedEnum.java.golden index 68d782621..06f6e3eb8 100644 --- a/tests/golden/java/java-okhttp/enum-repr/models/MixedEnum.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/models/MixedEnum.java.golden @@ -38,21 +38,34 @@ final class MixedEnumJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public MixedEnum read (JsonReader reader) throws java.io.IOException { + public MixedEnum read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() && element.getAsString().equals("SimpleA")) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected < 0 && matches5(element)) { + selected = 2; + } + if (selected < 0 && matches6(element)) { + selected = 3; + } + if (selected == 0) { return new MixedEnum.VariantSimpleA(element.getAsString()); } - if ((element.isJsonObject() && element.getAsJsonObject().has("VariantA"))) { + if (selected == 1) { return new MixedEnum.VariantMixedEnumMember2(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() && element.getAsString().equals("SimpleB")) { + if (selected == 2) { return new MixedEnum.VariantSimpleB(element.getAsString()); } - if ((element.isJsonObject() && element.getAsJsonObject().has("VariantB"))) { + if (selected == 3) { return new MixedEnum.VariantMixedEnumMember4(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown MixedEnum union value"); @@ -81,4 +94,93 @@ final class MixedEnumJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("SimpleA"); + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("VariantA") || !matches2(object.get("VariantA"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("field1") || !matches3(object.get("field1"))) { + return false; + } + if (!object.has("field2") || !matches4(object.get("field2"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("SimpleB"); + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("VariantB") || !matches7(object.get("VariantB"))) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("field3") || !matches8(object.get("field3"))) { + return false; + } + if (!object.has("field4") || !matches9(object.get("field4"))) { + return false; + } + return true; + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches9(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden b/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden index baf5ccb37..babdbc970 100644 --- a/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden +++ b/tests/golden/java/java-okhttp/enum-repr/models/UntaggedEnum.java.golden @@ -34,15 +34,22 @@ final class UntaggedEnumJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public UntaggedEnum read (JsonReader reader) throws java.io.IOException { + public UntaggedEnum read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (((element.isJsonObject() && element.getAsJsonObject().has("field1")) && element.getAsJsonObject().has("field2"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected == 0) { return new UntaggedEnum.VariantVariantA(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() && element.getAsJsonObject().has("field3")) && element.getAsJsonObject().has("field4"))) { + if (selected == 1) { return new UntaggedEnum.VariantVariantB(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown UntaggedEnum union value"); @@ -63,4 +70,63 @@ final class UntaggedEnumJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("field1") || !matches1(object.get("field1"))) { + return false; + } + if (!object.has("field2") || !matches2(object.get("field2"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("field3") || !matches4(object.get("field3"))) { + return false; + } + if (!object.has("field4") || !matches5(object.get("field4"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden b/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden index e52641ff7..c412bc1fd 100644 --- a/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-closed-sum-payload-shapes/models/Choice.java.golden @@ -38,18 +38,28 @@ final class ChoiceJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Choice read (JsonReader reader) throws java.io.IOException { + public Choice read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return Choice.VariantNull.INSTANCE; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected < 0 && matches3(element)) { + selected = 2; + } + if (selected == 0) { return new Choice.VariantString(element.getAsString()); } - if (element.isJsonArray()) { + if (selected == 1) { return new Choice.VariantArray(gson.fromJson(element, new TypeToken>() {}.getType())); } - if (element.isJsonObject()) { + if (selected == 2) { return new Choice.VariantMap(gson.fromJson(element, new TypeToken>() {}.getType())); } @@ -79,4 +89,55 @@ final class ChoiceJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches4(item)) { + return false; + } + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden index c2716ba7b..f691ed7c2 100644 --- a/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-complex-union/models/Foo.java.golden @@ -30,18 +30,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if ((((element.isJsonObject() && element.getAsJsonObject().has("bucket")) && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected < 0 && matches5(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + if (selected == 1) { return new Foo.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((((element.isJsonObject() && element.getAsJsonObject().has("namespace")) && element.getAsJsonObject().has("type")) && element.getAsJsonObject().has("url"))) { + if (selected == 2) { return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); @@ -66,4 +76,68 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("bucket") || !matches1(object.get("bucket"))) { + return false; + } + if (!object.has("endpoint") || !matches1(object.get("endpoint"))) { + return false; + } + if (!object.has("type") || !matches2(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("bar"); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("path") || !matches1(object.get("path"))) { + return false; + } + if (!object.has("type") || !matches4(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("baz"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("namespace") || !matches1(object.get("namespace"))) { + return false; + } + if (!object.has("type") || !matches6(object.get("type"))) { + return false; + } + if (!object.has("url") || !matches1(object.get("url"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("qux"); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden index e5c8c4e2b..3581cfd1f 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Foo.java.golden @@ -30,18 +30,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if ((((element.isJsonObject() && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type")) || ((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type")))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected < 0 && matches6(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + if (selected == 1) { return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 2) { return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown Foo union value"); @@ -66,4 +76,58 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches4(candidate)) { + return true; + } + return false; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("endpoint") || !matches2(object.get("endpoint"))) { + return false; + } + if (!object.has("type") || !matches3(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("bar"); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("path") || !matches2(object.get("path"))) { + return false; + } + if (!object.has("type") || !matches5(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("baz"); + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden index c9f9be92d..ba28992d8 100644 --- a/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-nested-union/models/Qux.java.golden @@ -28,15 +28,22 @@ final class QuxJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Qux read (JsonReader reader) throws java.io.IOException { + public Qux read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (((element.isJsonObject() && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected == 0) { return new Qux.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + if (selected == 1) { return new Qux.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Qux union value"); @@ -57,4 +64,44 @@ final class QuxJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("endpoint") || !matches1(object.get("endpoint"))) { + return false; + } + if (!object.has("type") || !matches2(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("bar"); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("path") || !matches1(object.get("path"))) { + return false; + } + if (!object.has("type") || !matches4(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("baz"); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden index 7c1c2ae15..4b1ba9c0c 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-mixed/models/Foo.java.golden @@ -30,18 +30,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (((element.isJsonObject() && element.getAsJsonObject().has("id")) && element.getAsJsonObject().has("name"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected < 0 && matches3(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + if (selected == 1) { return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 2) { return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } throw new JsonParseException("unknown Foo union value"); @@ -66,4 +76,45 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("id") || !matches1(object.get("id"))) { + return false; + } + if (!object.has("name") || !matches2(object.get("name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden index cd387381f..5fb9a54c7 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-any/models/Config.java.golden @@ -30,18 +30,31 @@ final class ConfigJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Config read (JsonReader reader) throws java.io.IOException { + public Config read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected < 0 && true) { + selected = 2; + } + if (selected == 0) { return new Config.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 1) { return new Config.VariantNumber(gson.fromJson(element, Double.class)); } - return new Config.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + if (selected == 2) { + return new Config.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Config union value"); } @Override @@ -63,4 +76,12 @@ final class ConfigJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden index c1a74dd6a..fef66f00c 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-inline-objects/models/Foo.java.golden @@ -33,18 +33,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if ((element.isJsonObject() && element.getAsJsonObject().has("bar"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected < 0 && matches5(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantFooMember1(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((element.isJsonObject() && element.getAsJsonObject().has("quux"))) { + if (selected == 1) { return new Foo.VariantFooMember2(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((element.isJsonObject() && element.getAsJsonObject().has("grault"))) { + if (selected == 2) { return new Foo.VariantFooMember3(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); @@ -69,4 +79,77 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("bar") || !matches1(object.get("bar"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("baz") || !matches2(object.get("baz"))) { + return false; + } + if (!object.has("qux") || !matches2(object.get("qux"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("quux") || !matches4(object.get("quux"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("corge") || !matches2(object.get("corge"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("grault") || !matches6(object.get("grault"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("garply") || !matches2(object.get("garply"))) { + return false; + } + return true; + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden index ea718e521..900d0a868 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-interfaces/models/Foo.java.golden @@ -30,18 +30,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return null; } - if ((((element.isJsonObject() && element.getAsJsonObject().has("bucket")) && element.getAsJsonObject().has("endpoint")) && element.getAsJsonObject().has("type"))) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected < 0 && matches5(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantBar(gson.fromJson(element, new TypeToken() {}.getType())); } - if (((element.isJsonObject() && element.getAsJsonObject().has("path")) && element.getAsJsonObject().has("type"))) { + if (selected == 1) { return new Foo.VariantBaz(gson.fromJson(element, new TypeToken() {}.getType())); } - if ((((element.isJsonObject() && element.getAsJsonObject().has("namespace")) && element.getAsJsonObject().has("type")) && element.getAsJsonObject().has("url"))) { + if (selected == 2) { return new Foo.VariantQux(gson.fromJson(element, new TypeToken() {}.getType())); } throw new JsonParseException("unknown Foo union value"); @@ -66,4 +76,68 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("bucket") || !matches1(object.get("bucket"))) { + return false; + } + if (!object.has("endpoint") || !matches1(object.get("endpoint"))) { + return false; + } + if (!object.has("type") || !matches2(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("bar"); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("path") || !matches1(object.get("path"))) { + return false; + } + if (!object.has("type") || !matches4(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("baz"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("namespace") || !matches1(object.get("namespace"))) { + return false; + } + if (!object.has("type") || !matches6(object.get("type"))) { + return false; + } + if (!object.has("url") || !matches1(object.get("url"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("qux"); + } } diff --git a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden index 32c0ace65..e469da47b 100644 --- a/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden +++ b/tests/golden/java/java-okhttp/type-aliases-union-with-primitives/models/Foo.java.golden @@ -35,18 +35,28 @@ final class FooJsonAdapter extends TypeAdapter { private final Gson gson = new Gson(); @Override - public Foo read (JsonReader reader) throws java.io.IOException { + public Foo read(JsonReader reader) throws java.io.IOException { JsonElement element = JsonParser.parseReader(reader); if (element.isJsonNull()) { return Foo.VariantNull.INSTANCE; } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected < 0 && matches2(element)) { + selected = 2; + } + if (selected == 0) { return new Foo.VariantString(element.getAsString()); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber()) { + if (selected == 1) { return new Foo.VariantNumber(gson.fromJson(element, Double.class)); } - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isBoolean()) { + if (selected == 2) { return new Foo.VariantBoolean(element.getAsBoolean()); } throw new JsonParseException("unknown Foo union value"); @@ -75,4 +85,16 @@ final class FooJsonAdapter extends TypeAdapter { return; } } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } } diff --git a/tests/golden/java/java-okhttp/union-first-match/README.md.golden b/tests/golden/java/java-okhttp/union-first-match/README.md.golden new file mode 100644 index 000000000..7dfcb9f54 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/README.md.golden @@ -0,0 +1,7 @@ +# Java union policies + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `java-union-policies`. diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceBadRequest.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceBadRequest.java.golden new file mode 100644 index 000000000..98bbfc562 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceBadRequest.java.golden @@ -0,0 +1,40 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +public final class GetChoiceBadRequest implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceBadRequest(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + + private String textBody() { + String contentTypeHeader = this.headers.get("Content-Type"); + okhttp3.MediaType contentType = contentTypeHeader != null ? okhttp3.MediaType.parse(contentTypeHeader) : null; + java.nio.charset.Charset charset = contentType != null ? contentType.charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + return new String(this.rawBody, charset); + } + public FiniteChoice body() { + String text = textBody(); + return new Gson().fromJson(text.isEmpty() ? "null" : text, new TypeToken() {}.getType()); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceException.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceException.java.golden new file mode 100644 index 000000000..350e4f2f3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceException.java.golden @@ -0,0 +1,33 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import com.example.sdk.runtime.ApiException; +import okhttp3.Headers; + +public final class GetChoiceException extends ApiException { + private final GetChoiceExceptionDetail detail; + private final Headers headers; + private final byte[] rawBody; + + public GetChoiceException(int statusCode, String status, String body, Headers headers, byte[] rawBody, GetChoiceExceptionDetail detail) { + super(statusCode, status, body); + this.headers = headers; + this.rawBody = rawBody.clone(); + this.detail = detail; + } + + public GetChoiceExceptionDetail detail() { + return this.detail; + } + + public Headers headers() { + return this.headers; + } + + public byte[] rawBody() { + return this.rawBody.clone(); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceExceptionDetail.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceExceptionDetail.java.golden new file mode 100644 index 000000000..b12c43230 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceExceptionDetail.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public sealed interface GetChoiceExceptionDetail permits GetChoiceBadRequest, GetChoiceUnexpected { + int statusCode(); + okhttp3.Headers headers(); + byte[] rawBody(); +} diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceResponse.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceResponse.java.golden new file mode 100644 index 000000000..72ed677a2 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceResponse.java.golden @@ -0,0 +1,38 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import okhttp3.Response; + +/** + * GetChoiceResponse carries the response from getChoice. + */ +public class GetChoiceResponse { + private final int statusCode; + private final Response raw; + private final FiniteChoice status200; + + public GetChoiceResponse(int statusCode, Response raw, FiniteChoice status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public FiniteChoice getStatus200() { + return this.status200; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceUnexpected.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceUnexpected.java.golden new file mode 100644 index 000000000..f67d0f3e9 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/GetChoiceUnexpected.java.golden @@ -0,0 +1,22 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public final class GetChoiceUnexpected implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceUnexpected(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + public byte[] body() { return this.rawBody.clone(); } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/apis/SelectionApi.java.golden b/tests/golden/java/java-okhttp/union-first-match/apis/SelectionApi.java.golden new file mode 100644 index 000000000..ebac5b271 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/apis/SelectionApi.java.golden @@ -0,0 +1,63 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.Request; +import okhttp3.Response; + +/** + * SelectionApi groups operations under the Selection tag. + */ +public class SelectionApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public SelectionApi(ApiClient client) { + this.client = client; + } + + /** + * getChoice GET /choice. + */ + public GetChoiceResponse getChoice() throws IOException { + String path = "/choice"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + okhttp3.ResponseBody responseBody = response.body(); + java.nio.charset.Charset responseCharset = responseBody != null && responseBody.contentType() != null ? responseBody.contentType().charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + byte[] responseBytes = responseBody != null ? responseBody.bytes() : new byte[0]; + String responseText = new String(responseBytes, responseCharset); + GetChoiceExceptionDetail detail; + switch (response.code()) { + case 400: + detail = new GetChoiceBadRequest(response.code(), response.headers(), responseBytes);break; + default: + detail = new GetChoiceUnexpected(response.code(), response.headers(), responseBytes); + } + throw new GetChoiceException(response.code(), response.message(), responseText, response.headers(), responseBytes, detail); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + FiniteChoice status200 = null; + if (response.code() == 200) { + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); + } + return new GetChoiceResponse(response.code(), response, status200); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/build.gradle.golden b/tests/golden/java/java-okhttp/union-first-match/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/AliasChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/AliasChoice.java.golden new file mode 100644 index 000000000..d5fc8073a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/AliasChoice.java.golden @@ -0,0 +1,114 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AliasChoiceJsonAdapter.class, nullSafe = false) +public sealed interface AliasChoice { + record VariantNumberAlias(NumberAlias value0) implements AliasChoice {} + + record VariantAlpha(Alpha value0) implements AliasChoice {} +} + +final class AliasChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public AliasChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected == 0) { + return new AliasChoice.VariantNumberAlias(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new AliasChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown AliasChoice union value"); + } + + @Override + public void write(JsonWriter writer, AliasChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof AliasChoice.VariantNumberAlias) { + gson.toJson(gson.toJsonTree(((AliasChoice.VariantNumberAlias) value).value0()), writer); + return; + } + if (value instanceof AliasChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((AliasChoice.VariantAlpha) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("value") || !matches1(object.get("value"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches3(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Alpha.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Alpha.java.golden new file mode 100644 index 000000000..b33314a36 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Alpha.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Alpha { + @SerializedName("first_value") + private String firstValue; + @SerializedName("wire_name") + private String wireName; + + public Alpha(String firstValue, String wireName) { + this.firstValue = firstValue; + this.wireName = wireName; + } + + public String getFirstValue() { + return this.firstValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Ambiguous.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Ambiguous.java.golden new file mode 100644 index 000000000..74eba59db --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Ambiguous.java.golden @@ -0,0 +1,98 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AmbiguousJsonAdapter.class, nullSafe = false) +public sealed interface Ambiguous { + record VariantAlpha(Alpha value0) implements Ambiguous {} + + record VariantBeta(Beta value0) implements Ambiguous {} +} + +final class AmbiguousJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Ambiguous read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected == 0) { + return new Ambiguous.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Ambiguous.VariantBeta(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Ambiguous union value"); + } + + @Override + public void write(JsonWriter writer, Ambiguous value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Ambiguous.VariantAlpha) { + gson.toJson(gson.toJsonTree(((Ambiguous.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof Ambiguous.VariantBeta) { + gson.toJson(gson.toJsonTree(((Ambiguous.VariantBeta) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/AmbiguousAlias.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/AmbiguousAlias.java.golden new file mode 100644 index 000000000..5cd6ab3b6 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/AmbiguousAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class AmbiguousAlias { + private Ambiguous value; + + public AmbiguousAlias(Ambiguous value) { + this.value = value; + } + + public Ambiguous getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Beta.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Beta.java.golden new file mode 100644 index 000000000..71ed91106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Beta.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Beta { + @SerializedName("second_value") + private String secondValue; + @SerializedName("wire_name") + private String wireName; + + public Beta(String secondValue, String wireName) { + this.secondValue = secondValue; + this.wireName = wireName; + } + + public String getSecondValue() { + return this.secondValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/BinaryChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/BinaryChoice.java.golden new file mode 100644 index 000000000..d03dfe62c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/BinaryChoice.java.golden @@ -0,0 +1,106 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = BinaryChoiceJsonAdapter.class, nullSafe = false) +public sealed interface BinaryChoice { + record VariantBinary(byte[] value0) implements BinaryChoice {} + + record VariantString(String value0) implements BinaryChoice {} +} + +final class BinaryChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public BinaryChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches1(element)) { + selected = 1; + } + if (selected == 0) { + return new BinaryChoice.VariantBinary(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new BinaryChoice.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown BinaryChoice union value"); + } + + @Override + public void write(JsonWriter writer, BinaryChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof BinaryChoice.VariantBinary) { + gson.toJson(gson.toJsonTree(((BinaryChoice.VariantBinary) value).value0()), writer); + return; + } + if (value instanceof BinaryChoice.VariantString) { + gson.toJson(gson.toJsonTree(((BinaryChoice.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!item.isJsonPrimitive() || !item.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = item.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + if (significand.signum() == 0) { + continue; + } + java.math.BigInteger scale = java.math.BigInteger.valueOf(significand.scale()).subtract(numberExponent(literal)); + + if (scale.signum() > 0 || scale.compareTo(java.math.BigInteger.valueOf(-2)) < 0) { + return false; + } + java.math.BigDecimal number = new java.math.BigDecimal(significand.unscaledValue(), scale.intValueExact()); + if (number.compareTo(new java.math.BigDecimal("-128")) < 0 || number.compareTo(new java.math.BigDecimal("255")) > 0) { + return false; + } + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/BranchOne.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/BranchOne.java.golden new file mode 100644 index 000000000..e03483640 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/BranchOne.java.golden @@ -0,0 +1,25 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class BranchOne { + private List children; + private int revision; + + public BranchOne(List children, int revision) { + this.children = children; + this.revision = revision; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/BranchTwo.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/BranchTwo.java.golden new file mode 100644 index 000000000..3453b6947 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/BranchTwo.java.golden @@ -0,0 +1,34 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class BranchTwo { + private List children; + private int revision; + @SerializedName("second_value") + private String secondValue; + + public BranchTwo(List children, int revision, String secondValue) { + this.children = children; + this.revision = revision; + this.secondValue = secondValue; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ChoiceEnvelope.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ChoiceEnvelope.java.golden new file mode 100644 index 000000000..4f0cd8c61 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ChoiceEnvelope.java.golden @@ -0,0 +1,49 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class ChoiceEnvelope { + @SerializedName("by_name") + private Map byName; + private String caption; + private FiniteChoice choice; + private List choices; + @SerializedName("ordinary_null") + private String ordinaryNull; + + public ChoiceEnvelope(Map byName, String caption, FiniteChoice choice, + List choices, String ordinaryNull) { + this.byName = byName; + this.caption = caption; + this.choice = choice; + this.choices = choices; + this.ordinaryNull = ordinaryNull; + } + + public Map getByName() { + return this.byName; + } + + public String getCaption() { + return this.caption; + } + + public FiniteChoice getChoice() { + return this.choice; + } + + public List getChoices() { + return this.choices; + } + + public String getOrdinaryNull() { + return this.ordinaryNull; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ContainerChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ContainerChoice.java.golden new file mode 100644 index 000000000..d9328b9ad --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ContainerChoice.java.golden @@ -0,0 +1,176 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ContainerChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ContainerChoice { + record VariantContainerOne(ContainerOne value0) implements ContainerChoice {} + + record VariantContainerTwo(ContainerTwo value0) implements ContainerChoice {} +} + +final class ContainerChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ContainerChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches5(element)) { + selected = 1; + } + if (selected == 0) { + return new ContainerChoice.VariantContainerOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new ContainerChoice.VariantContainerTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown ContainerChoice union value"); + } + + @Override + public void write(JsonWriter writer, ContainerChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof ContainerChoice.VariantContainerOne) { + gson.toJson(gson.toJsonTree(((ContainerChoice.VariantContainerOne) value).value0()), writer); + return; + } + if (value instanceof ContainerChoice.VariantContainerTwo) { + gson.toJson(gson.toJsonTree(((ContainerChoice.VariantContainerTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("values") || !matches1(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches6(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches7(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches8(item)) { + return false; + } + } + return true; + } + + private static boolean matches8(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches9(candidate)) { + return false; + } + return true; + } + + private static boolean matches9(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ContainerOne.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ContainerOne.java.golden new file mode 100644 index 000000000..cba3f808a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ContainerOne.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class ContainerOne { + private List values; + + public ContainerOne(List values) { + this.values = values; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ContainerTwo.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ContainerTwo.java.golden new file mode 100644 index 000000000..29ce4e106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ContainerTwo.java.golden @@ -0,0 +1,28 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class ContainerTwo { + @SerializedName("second_value") + private String secondValue; + private List values; + + public ContainerTwo(String secondValue, List values) { + this.secondValue = secondValue; + this.values = values; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ExcludedNull.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ExcludedNull.java.golden new file mode 100644 index 000000000..d2f8299fe --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ExcludedNull.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class ExcludedNull { + private Integer revision; + + public ExcludedNull(Integer revision) { + this.revision = revision; + } + + public Integer getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/FiniteChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/FiniteChoice.java.golden new file mode 100644 index 000000000..cbeaef5ed --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/FiniteChoice.java.golden @@ -0,0 +1,299 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FiniteChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FiniteChoice { + record VariantFiniteOne(FiniteOne value0) implements FiniteChoice {} + + record VariantFiniteTwo(FiniteTwo value0) implements FiniteChoice {} +} + +final class FiniteChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FiniteChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches14(element)) { + selected = 1; + } + if (selected == 0) { + return new FiniteChoice.VariantFiniteOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FiniteChoice.VariantFiniteTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FiniteChoice union value"); + } + + @Override + public void write(JsonWriter writer, FiniteChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FiniteChoice.VariantFiniteOne) { + gson.toJson(gson.toJsonTree(((FiniteChoice.VariantFiniteOne) value).value0()), writer); + return; + } + if (value instanceof FiniteChoice.VariantFiniteTwo) { + gson.toJson(gson.toJsonTree(((FiniteChoice.VariantFiniteTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("flags") || !matches2(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches6(object.get("revision"))) { + return false; + } + if (!object.has("values") || !matches9(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches3(item)) { + return false; + } + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == false; + } + + private static boolean matches6(JsonElement candidate) { + if (!matches7(candidate)) { + return false; + } + if (!matches8(candidate)) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches9(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches10(item)) { + return false; + } + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches12(candidate)) { + return false; + } + return true; + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches12(JsonElement candidate) { + if (matches8(candidate)) { + return true; + } + if (matches13(candidate)) { + return true; + } + return false; + } + + private static boolean matches13(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1.5"); + } + + private static boolean matches14(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("flags") || !matches15(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches18(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches20(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches15(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches16(item)) { + return false; + } + } + return true; + } + + private static boolean matches16(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches17(candidate)) { + return false; + } + return true; + } + + private static boolean matches17(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches18(JsonElement candidate) { + if (!matches7(candidate)) { + return false; + } + if (!matches19(candidate)) { + return false; + } + return true; + } + + private static boolean matches19(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches20(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches21(item)) { + return false; + } + } + return true; + } + + private static boolean matches21(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches22(candidate)) { + return false; + } + return true; + } + + private static boolean matches22(JsonElement candidate) { + if (matches19(candidate)) { + return true; + } + if (matches23(candidate)) { + return true; + } + return false; + } + + private static boolean matches23(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/FiniteOne.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/FiniteOne.java.golden new file mode 100644 index 000000000..c4b41b025 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/FiniteOne.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteOne { + @SerializedName("first_value") + private String firstValue; + private Map flags; + private int revision; + private List values; + + public FiniteOne(String firstValue, Map flags, int revision, + List values) { + this.firstValue = firstValue; + this.flags = flags; + this.revision = revision; + this.values = values; + } + + public String getFirstValue() { + return this.firstValue; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/FiniteTwo.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/FiniteTwo.java.golden new file mode 100644 index 000000000..b918dda08 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/FiniteTwo.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteTwo { + private Map flags; + private int revision; + @SerializedName("second_value") + private String secondValue; + private List values; + + public FiniteTwo(Map flags, int revision, String secondValue, + List values) { + this.flags = flags; + this.revision = revision; + this.secondValue = secondValue; + this.values = values; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/FormatChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/FormatChoice.java.golden new file mode 100644 index 000000000..55bcfa832 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/FormatChoice.java.golden @@ -0,0 +1,114 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FormatChoice { + record VariantNarrow(Narrow value0) implements FormatChoice {} + + record VariantWide(Wide value0) implements FormatChoice {} +} + +final class FormatChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected == 0) { + return new FormatChoice.VariantNarrow(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FormatChoice.VariantWide(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FormatChoice union value"); + } + + @Override + public void write(JsonWriter writer, FormatChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FormatChoice.VariantNarrow) { + gson.toJson(gson.toJsonTree(((FormatChoice.VariantNarrow) value).value0()), writer); + return; + } + if (value instanceof FormatChoice.VariantWide) { + gson.toJson(gson.toJsonTree(((FormatChoice.VariantWide) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + if (object.has("second_value") && !matches3(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/FormatFallback.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/FormatFallback.java.golden new file mode 100644 index 000000000..c9edb26f0 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/FormatFallback.java.golden @@ -0,0 +1,96 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatFallbackJsonAdapter.class, nullSafe = false) +public sealed interface FormatFallback { + record VariantNarrow(Narrow value0) implements FormatFallback {} + + record VariantAny(JsonElement value0) implements FormatFallback {} +} + +final class FormatFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new FormatFallback.VariantNarrow(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FormatFallback.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FormatFallback union value"); + } + + @Override + public void write(JsonWriter writer, FormatFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FormatFallback.VariantNarrow) { + gson.toJson(gson.toJsonTree(((FormatFallback.VariantNarrow) value).value0()), writer); + return; + } + if (value instanceof FormatFallback.VariantAny) { + gson.toJson(gson.toJsonTree(((FormatFallback.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/IncludedNull.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/IncludedNull.java.golden new file mode 100644 index 000000000..42b84c965 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/IncludedNull.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class IncludedNull { + private Integer revision; + @SerializedName("second_value") + private String secondValue; + + public IncludedNull(Integer revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public Integer getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/IntegerFallback.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/IntegerFallback.java.golden new file mode 100644 index 000000000..76dcb7a29 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/IntegerFallback.java.golden @@ -0,0 +1,85 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = IntegerFallbackJsonAdapter.class, nullSafe = false) +public sealed interface IntegerFallback { + record VariantInteger(int value0) implements IntegerFallback {} + + record VariantAny(JsonElement value0) implements IntegerFallback {} +} + +final class IntegerFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public IntegerFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new IntegerFallback.VariantInteger(gson.fromJson(element, Integer.class)); + } + if (selected == 1) { + return new IntegerFallback.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown IntegerFallback union value"); + } + + @Override + public void write(JsonWriter writer, IntegerFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof IntegerFallback.VariantInteger) { + gson.toJson(gson.toJsonTree(((IntegerFallback.VariantInteger) value).value0()), writer); + return; + } + if (value instanceof IntegerFallback.VariantAny) { + gson.toJson(gson.toJsonTree(((IntegerFallback.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/LargeChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/LargeChoice.java.golden new file mode 100644 index 000000000..7b74ba668 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/LargeChoice.java.golden @@ -0,0 +1,152 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = LargeChoiceJsonAdapter.class, nullSafe = false) +public sealed interface LargeChoice { + record VariantLargeOne(LargeOne value0) implements LargeChoice {} + + record VariantLargeTwo(LargeTwo value0) implements LargeChoice {} +} + +final class LargeChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public LargeChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches4(element)) { + selected = 1; + } + if (selected == 0) { + return new LargeChoice.VariantLargeOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new LargeChoice.VariantLargeTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown LargeChoice union value"); + } + + @Override + public void write(JsonWriter writer, LargeChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof LargeChoice.VariantLargeOne) { + gson.toJson(gson.toJsonTree(((LargeChoice.VariantLargeOne) value).value0()), writer); + return; + } + if (value instanceof LargeChoice.VariantLargeTwo) { + gson.toJson(gson.toJsonTree(((LargeChoice.VariantLargeTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches1(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "9007199254740992"); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches7(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "9007199254740993"); + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/LargeOne.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/LargeOne.java.golden new file mode 100644 index 000000000..b48a90e70 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/LargeOne.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class LargeOne { + private long revision; + + public LargeOne(long revision) { + this.revision = revision; + } + + public long getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/LargeTwo.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/LargeTwo.java.golden new file mode 100644 index 000000000..4d5431a72 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/LargeTwo.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LargeTwo { + private long revision; + @SerializedName("second_value") + private String secondValue; + + public LargeTwo(long revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public long getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/LeftPart.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/LeftPart.java.golden new file mode 100644 index 000000000..bd3898bb7 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/LeftPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LeftPart { + @SerializedName("left_value") + private String leftValue; + + public LeftPart(String leftValue) { + this.leftValue = leftValue; + } + + public String getLeftValue() { + return this.leftValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Narrow.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Narrow.java.golden new file mode 100644 index 000000000..59c5707b8 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Narrow.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class Narrow { + private int identifier; + + public Narrow(int identifier) { + this.identifier = identifier; + } + + public int getIdentifier() { + return this.identifier; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/NullableChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/NullableChoice.java.golden new file mode 100644 index 000000000..2b141724b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/NullableChoice.java.golden @@ -0,0 +1,81 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableChoiceJsonAdapter.class, nullSafe = false) +public sealed interface NullableChoice { + record VariantAlpha(Alpha value0) implements NullableChoice {} + + /** + * The union's null value. + */ + enum VariantNull implements NullableChoice { INSTANCE } +} + +final class NullableChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return NullableChoice.VariantNull.INSTANCE; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected == 0) { + return new NullableChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NullableChoice union value"); + } + + @Override + public void write(JsonWriter writer, NullableChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NullableChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((NullableChoice.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof NullableChoice.VariantNull) { + writer.nullValue(); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/NullableFields.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/NullableFields.java.golden new file mode 100644 index 000000000..2ad1a70ca --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/NullableFields.java.golden @@ -0,0 +1,184 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableFieldsJsonAdapter.class, nullSafe = false) +public sealed interface NullableFields { + record VariantExcludedNull(ExcludedNull value0) implements NullableFields {} + + record VariantIncludedNull(IncludedNull value0) implements NullableFields {} +} + +final class NullableFieldsJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableFields read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches6(element)) { + selected = 1; + } + if (selected == 0) { + return new NullableFields.VariantExcludedNull(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new NullableFields.VariantIncludedNull(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NullableFields union value"); + } + + @Override + public void write(JsonWriter writer, NullableFields value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NullableFields.VariantExcludedNull) { + gson.toJson(gson.toJsonTree(((NullableFields.VariantExcludedNull) value).value0()), writer); + return; + } + if (value instanceof NullableFields.VariantIncludedNull) { + gson.toJson(gson.toJsonTree(((NullableFields.VariantIncludedNull) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches1(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonNull() || matches3(candidate); + } + + private static boolean matches3(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches7(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches12(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + if (!matches8(candidate)) { + return false; + } + if (!matches9(candidate)) { + return false; + } + return true; + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonNull() || matches4(candidate); + } + + private static boolean matches9(JsonElement candidate) { + if (matches10(candidate)) { + return true; + } + if (matches11(candidate)) { + return true; + } + return false; + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonNull(); + } + + private static boolean matches12(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/NumberAlias.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/NumberAlias.java.golden new file mode 100644 index 000000000..7aaa9bd2a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/NumberAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class NumberAlias { + private int value; + + public NumberAlias(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/NumericFinite.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/NumericFinite.java.golden new file mode 100644 index 000000000..fe4546cf3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/NumericFinite.java.golden @@ -0,0 +1,117 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericFiniteJsonAdapter.class, nullSafe = false) +public sealed interface NumericFinite { + record VariantNumber(double value0) implements NumericFinite {} + + record VariantAny(JsonElement value0) implements NumericFinite {} +} + +final class NumericFiniteJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericFinite read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new NumericFinite.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 1) { + return new NumericFinite.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NumericFinite union value"); + } + + @Override + public void write(JsonWriter writer, NumericFinite value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NumericFinite.VariantNumber) { + gson.toJson(gson.toJsonTree(((NumericFinite.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof NumericFinite.VariantAny) { + gson.toJson(gson.toJsonTree(((NumericFinite.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1.25"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/NumericZero.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/NumericZero.java.golden new file mode 100644 index 000000000..1d661c264 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/NumericZero.java.golden @@ -0,0 +1,117 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericZeroJsonAdapter.class, nullSafe = false) +public sealed interface NumericZero { + record VariantNumber(double value0) implements NumericZero {} + + record VariantAny(JsonElement value0) implements NumericZero {} +} + +final class NumericZeroJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericZero read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new NumericZero.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 1) { + return new NumericZero.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NumericZero union value"); + } + + @Override + public void write(JsonWriter writer, NumericZero value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NumericZero.VariantNumber) { + gson.toJson(gson.toJsonTree(((NumericZero.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof NumericZero.VariantAny) { + gson.toJson(gson.toJsonTree(((NumericZero.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "0"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/OpaqueFirst.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/OpaqueFirst.java.golden new file mode 100644 index 000000000..e3c995713 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/OpaqueFirst.java.golden @@ -0,0 +1,203 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpaqueFirstJsonAdapter.class, nullSafe = false) +public sealed interface OpaqueFirst { + record VariantAny(JsonElement value0) implements OpaqueFirst {} + + record VariantFiniteTwo(FiniteTwo value0) implements OpaqueFirst {} +} + +final class OpaqueFirstJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpaqueFirst read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 1; + } + if (selected < 0 && true) { + selected = 0; + } + if (selected == 0) { + return new OpaqueFirst.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new OpaqueFirst.VariantFiniteTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown OpaqueFirst union value"); + } + + @Override + public void write(JsonWriter writer, OpaqueFirst value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof OpaqueFirst.VariantAny) { + gson.toJson(gson.toJsonTree(((OpaqueFirst.VariantAny) value).value0()), writer); + return; + } + if (value instanceof OpaqueFirst.VariantFiniteTwo) { + gson.toJson(gson.toJsonTree(((OpaqueFirst.VariantFiniteTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("flags") || !matches1(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches8(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches9(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches2(item)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches9(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches10(item)) { + return false; + } + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches12(candidate)) { + return false; + } + return true; + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches12(JsonElement candidate) { + if (matches7(candidate)) { + return true; + } + if (matches13(candidate)) { + return true; + } + return false; + } + + private static boolean matches13(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/OpenNested.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/OpenNested.java.golden new file mode 100644 index 000000000..268b97cba --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/OpenNested.java.golden @@ -0,0 +1,119 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpenNestedJsonAdapter.class, nullSafe = false) +public sealed interface OpenNested { + record VariantAmbiguousAlias(AmbiguousAlias value0) implements OpenNested {} + + record VariantAny(JsonElement value0) implements OpenNested {} +} + +final class OpenNestedJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpenNested read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new OpenNested.VariantAmbiguousAlias(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new OpenNested.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown OpenNested union value"); + } + + @Override + public void write(JsonWriter writer, OpenNested value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof OpenNested.VariantAmbiguousAlias) { + gson.toJson(gson.toJsonTree(((OpenNested.VariantAmbiguousAlias) value).value0()), writer); + return; + } + if (value instanceof OpenNested.VariantAny) { + gson.toJson(gson.toJsonTree(((OpenNested.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("value") || !matches1(object.get("value"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (matches2(candidate)) { + return true; + } + if (matches4(candidate)) { + return true; + } + return false; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches3(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches3(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/RecursiveChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/RecursiveChoice.java.golden new file mode 100644 index 000000000..437d5c59c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/RecursiveChoice.java.golden @@ -0,0 +1,67 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = RecursiveChoiceJsonAdapter.class, nullSafe = false) +public sealed interface RecursiveChoice { + record VariantRecursiveChoice(RecursiveChoice value0) implements RecursiveChoice {} + + record VariantString(String value0) implements RecursiveChoice {} +} + +final class RecursiveChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public RecursiveChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 1; + } + if (selected == 0) { + return new RecursiveChoice.VariantRecursiveChoice(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new RecursiveChoice.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown RecursiveChoice union value"); + } + + @Override + public void write(JsonWriter writer, RecursiveChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof RecursiveChoice.VariantRecursiveChoice) { + gson.toJson(gson.toJsonTree(((RecursiveChoice.VariantRecursiveChoice) value).value0()), writer); + return; + } + if (value instanceof RecursiveChoice.VariantString) { + gson.toJson(gson.toJsonTree(((RecursiveChoice.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/RightPart.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/RightPart.java.golden new file mode 100644 index 000000000..4ab795f40 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/RightPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class RightPart { + @SerializedName("right_value") + private int rightValue; + + public RightPart(int rightValue) { + this.rightValue = rightValue; + } + + public int getRightValue() { + return this.rightValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/ScalarChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/ScalarChoice.java.golden new file mode 100644 index 000000000..608874301 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/ScalarChoice.java.golden @@ -0,0 +1,196 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ScalarChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ScalarChoice { + record VariantInteger(int value0) implements ScalarChoice {} + + record VariantNumber(double value0) implements ScalarChoice {} + + record VariantBoolean(boolean value0) implements ScalarChoice {} +} + +final class ScalarChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ScalarChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches4(element)) { + selected = 1; + } + if (selected < 0 && matches8(element)) { + selected = 2; + } + if (selected == 0) { + return new ScalarChoice.VariantInteger(gson.fromJson(element, Integer.class)); + } + if (selected == 1) { + return new ScalarChoice.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 2) { + return new ScalarChoice.VariantBoolean(element.getAsBoolean()); + } + throw new JsonParseException("unknown ScalarChoice union value"); + } + + @Override + public void write(JsonWriter writer, ScalarChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof ScalarChoice.VariantInteger) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantInteger) value).value0()), writer); + return; + } + if (value instanceof ScalarChoice.VariantNumber) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof ScalarChoice.VariantBoolean) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantBoolean) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate) { + if (!matches5(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static boolean matches7(JsonElement candidate) { + if (matches5(candidate)) { + return true; + } + if (matches6(candidate)) { + return true; + } + return false; + } + + private static boolean matches8(JsonElement candidate) { + if (!matches9(candidate)) { + return false; + } + if (!matches10(candidate)) { + return false; + } + if (!matches11(candidate)) { + return false; + } + return true; + } + + private static boolean matches9(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches11(JsonElement candidate) { + if (matches9(candidate)) { + return true; + } + if (matches10(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Tree.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Tree.java.golden new file mode 100644 index 000000000..22cb10750 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Tree.java.golden @@ -0,0 +1,204 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = TreeJsonAdapter.class, nullSafe = false) +public sealed interface Tree { + record VariantBranchOne(BranchOne value0) implements Tree {} + + record VariantBranchTwo(BranchTwo value0) implements Tree {} +} + +final class TreeJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tree read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + java.util.IdentityHashMap> memo = new java.util.IdentityHashMap>(); + if (selected < 0 && matches0(element, memo)) { + selected = 0; + } + if (selected < 0 && matches4(element, memo)) { + selected = 1; + } + if (selected == 0) { + return new Tree.VariantBranchOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Tree.VariantBranchTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Tree union value"); + } + + @Override + public void write(JsonWriter writer, Tree value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Tree.VariantBranchOne) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranchOne) value).value0()), writer); + return; + } + if (value instanceof Tree.VariantBranchTwo) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranchTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + if (!object.has("revision") || !matches9(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item, memo)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate, java.util.IdentityHashMap> memo) { + java.util.Map results = memo.get(candidate); + if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); + } + java.lang.Boolean cached = results.get(2); + if (cached != null) { + return cached; + } + boolean matched = matches3(candidate, memo); + results.put(2, matched); + return matched; + } + + private static boolean matches3(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (matches0(candidate, memo)) { + return true; + } + if (matches4(candidate, memo)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches8(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches9(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches10(candidate)) { + return false; + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/VariantRaw.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/VariantRaw.java.golden new file mode 100644 index 000000000..3427367ae --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/VariantRaw.java.golden @@ -0,0 +1,98 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = VariantRawJsonAdapter.class, nullSafe = false) +public sealed interface VariantRaw { + record VariantAlpha(Alpha value0) implements VariantRaw {} + + record VariantBeta(Beta value0) implements VariantRaw {} +} + +final class VariantRawJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public VariantRaw read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches2(element)) { + selected = 1; + } + if (selected == 0) { + return new VariantRaw.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new VariantRaw.VariantBeta(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown VariantRaw union value"); + } + + @Override + public void write(JsonWriter writer, VariantRaw value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof VariantRaw.VariantAlpha) { + gson.toJson(gson.toJsonTree(((VariantRaw.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof VariantRaw.VariantBeta) { + gson.toJson(gson.toJsonTree(((VariantRaw.VariantBeta) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/Wide.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/Wide.java.golden new file mode 100644 index 000000000..c64abc094 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/Wide.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Wide { + private long identifier; + @SerializedName("second_value") + private String secondValue; + + public Wide(long identifier, String secondValue) { + this.identifier = identifier; + this.secondValue = secondValue; + } + + public long getIdentifier() { + return this.identifier; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/WrappedChoice.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/WrappedChoice.java.golden new file mode 100644 index 000000000..63e128c92 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/WrappedChoice.java.golden @@ -0,0 +1,163 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = WrappedChoiceJsonAdapter.class, nullSafe = false) +public sealed interface WrappedChoice { + record VariantWrappedPair(WrappedPair value0) implements WrappedChoice {} + + record VariantAlpha(Alpha value0) implements WrappedChoice {} +} + +final class WrappedChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public WrappedChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches7(element)) { + selected = 1; + } + if (selected == 0) { + return new WrappedChoice.VariantWrappedPair(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new WrappedChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown WrappedChoice union value"); + } + + @Override + public void write(JsonWriter writer, WrappedChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof WrappedChoice.VariantWrappedPair) { + gson.toJson(gson.toJsonTree(((WrappedChoice.VariantWrappedPair) value).value0()), writer); + return; + } + if (value instanceof WrappedChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((WrappedChoice.VariantAlpha) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("leftPart") || !matches1(object.get("leftPart"))) { + return false; + } + if (!object.has("rightPart") || !matches3(object.get("rightPart"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("left_value") || !matches2(object.get("left_value"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("right_value") || !matches4(object.get("right_value"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!matches5(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches2(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches2(object.get("wire_name"))) { + return false; + } + return true; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/models/WrappedPair.java.golden b/tests/golden/java/java-okhttp/union-first-match/models/WrappedPair.java.golden new file mode 100644 index 000000000..18168e273 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/models/WrappedPair.java.golden @@ -0,0 +1,23 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class WrappedPair { + private LeftPart leftPart; + private RightPart rightPart; + + public WrappedPair(LeftPart leftPart, RightPart rightPart) { + this.leftPart = leftPart; + this.rightPart = rightPart; + } + + public LeftPart getLeftPart() { + return this.leftPart; + } + + public RightPart getRightPart() { + return this.rightPart; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiClient.java.golden new file mode 100644 index 000000000..c45cce839 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiException.java.golden new file mode 100644 index 000000000..b7ed957af --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..3b8992bdc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..242937e90 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/Authenticator.java.golden new file mode 100644 index 000000000..f03b10463 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-first-match/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-first-match/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..4154f770c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-first-match/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/README.md.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/README.md.golden new file mode 100644 index 000000000..6ad778d6b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/README.md.golden @@ -0,0 +1,7 @@ +# Java union helper types + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `java-union-helper-types`. diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/build.gradle.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/models/Boolean.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Boolean.java.golden new file mode 100644 index 000000000..82c8b4f26 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Boolean.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +public class Boolean { + private Object value; + + public Boolean(Object value) { + this.value = value; + } + + public Object getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/models/Branch.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Branch.java.golden new file mode 100644 index 000000000..2603e8877 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Branch.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class Branch { + private List children; + + public Branch(List children) { + this.children = children; + } + + public List getChildren() { + return this.children; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/models/Integer.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Integer.java.golden new file mode 100644 index 000000000..0fb928420 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Integer.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +public class Integer { + private Object value; + + public Integer(Object value) { + this.value = value; + } + + public Object getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/models/Tree.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Tree.java.golden new file mode 100644 index 000000000..8e114f4d4 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/models/Tree.java.golden @@ -0,0 +1,132 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = TreeJsonAdapter.class, nullSafe = false) +public sealed interface Tree { + record VariantBranch(Branch value0) implements Tree {} + + record VariantString(String value0) implements Tree {} +} + +final class TreeJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tree read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + java.util.IdentityHashMap> memo = new java.util.IdentityHashMap>(); + if (matches0(element, memo)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Tree: multiple concrete members match"); + } + selected = 0; + } + if (matches4(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Tree: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new Tree.VariantBranch(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Tree.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown Tree union value"); + } + + @Override + public void write(JsonWriter writer, Tree value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Tree.VariantBranch) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranch) value).value0()), writer); + return; + } + if (value instanceof Tree.VariantString) { + gson.toJson(gson.toJsonTree(((Tree.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item, memo)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate, java.util.IdentityHashMap> memo) { + java.util.Map results = memo.get(candidate); + if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); + } + java.lang.Boolean cached = results.get(2); + if (cached != null) { + return cached; + } + boolean matched = matches3(candidate, memo); + results.put(2, matched); + return matched; + } + + private static boolean matches3(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (matches0(candidate, memo)) { + return true; + } + if (matches4(candidate)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiClient.java.golden new file mode 100644 index 000000000..3b1784c21 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiException.java.golden new file mode 100644 index 000000000..fccd84990 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..d5dc9a042 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..c1dd5c9c5 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/Authenticator.java.golden new file mode 100644 index 000000000..aac384222 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..d19f48688 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types-strict/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/README.md.golden b/tests/golden/java/java-okhttp/union-helper-types/README.md.golden new file mode 100644 index 000000000..6ad778d6b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/README.md.golden @@ -0,0 +1,7 @@ +# Java union helper types + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `java-union-helper-types`. diff --git a/tests/golden/java/java-okhttp/union-helper-types/build.gradle.golden b/tests/golden/java/java-okhttp/union-helper-types/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/models/Boolean.java.golden b/tests/golden/java/java-okhttp/union-helper-types/models/Boolean.java.golden new file mode 100644 index 000000000..82c8b4f26 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/models/Boolean.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +public class Boolean { + private Object value; + + public Boolean(Object value) { + this.value = value; + } + + public Object getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/models/Branch.java.golden b/tests/golden/java/java-okhttp/union-helper-types/models/Branch.java.golden new file mode 100644 index 000000000..2603e8877 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/models/Branch.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class Branch { + private List children; + + public Branch(List children) { + this.children = children; + } + + public List getChildren() { + return this.children; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/models/Integer.java.golden b/tests/golden/java/java-okhttp/union-helper-types/models/Integer.java.golden new file mode 100644 index 000000000..0fb928420 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/models/Integer.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +public class Integer { + private Object value; + + public Integer(Object value) { + this.value = value; + } + + public Object getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/models/Tree.java.golden b/tests/golden/java/java-okhttp/union-helper-types/models/Tree.java.golden new file mode 100644 index 000000000..5d7153eaf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/models/Tree.java.golden @@ -0,0 +1,126 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = TreeJsonAdapter.class, nullSafe = false) +public sealed interface Tree { + record VariantBranch(Branch value0) implements Tree {} + + record VariantString(String value0) implements Tree {} +} + +final class TreeJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tree read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + java.util.IdentityHashMap> memo = new java.util.IdentityHashMap>(); + if (selected < 0 && matches0(element, memo)) { + selected = 0; + } + if (selected < 0 && matches4(element)) { + selected = 1; + } + if (selected == 0) { + return new Tree.VariantBranch(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Tree.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown Tree union value"); + } + + @Override + public void write(JsonWriter writer, Tree value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Tree.VariantBranch) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranch) value).value0()), writer); + return; + } + if (value instanceof Tree.VariantString) { + gson.toJson(gson.toJsonTree(((Tree.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item, memo)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate, java.util.IdentityHashMap> memo) { + java.util.Map results = memo.get(candidate); + if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); + } + java.lang.Boolean cached = results.get(2); + if (cached != null) { + return cached; + } + boolean matched = matches3(candidate, memo); + results.put(2, matched); + return matched; + } + + private static boolean matches3(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (matches0(candidate, memo)) { + return true; + } + if (matches4(candidate)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiClient.java.golden new file mode 100644 index 000000000..3b1784c21 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiException.java.golden new file mode 100644 index 000000000..fccd84990 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..d5dc9a042 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..c1dd5c9c5 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/Authenticator.java.golden new file mode 100644 index 000000000..aac384222 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-helper-types/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-helper-types/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..d19f48688 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-helper-types/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union helper types — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/README.md.golden b/tests/golden/java/java-okhttp/union-raw/README.md.golden new file mode 100644 index 000000000..7dfcb9f54 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/README.md.golden @@ -0,0 +1,7 @@ +# Java union policies + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `java-union-policies`. diff --git a/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceBadRequest.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceBadRequest.java.golden new file mode 100644 index 000000000..98bbfc562 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceBadRequest.java.golden @@ -0,0 +1,40 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +public final class GetChoiceBadRequest implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceBadRequest(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + + private String textBody() { + String contentTypeHeader = this.headers.get("Content-Type"); + okhttp3.MediaType contentType = contentTypeHeader != null ? okhttp3.MediaType.parse(contentTypeHeader) : null; + java.nio.charset.Charset charset = contentType != null ? contentType.charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + return new String(this.rawBody, charset); + } + public FiniteChoice body() { + String text = textBody(); + return new Gson().fromJson(text.isEmpty() ? "null" : text, new TypeToken() {}.getType()); + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceException.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceException.java.golden new file mode 100644 index 000000000..350e4f2f3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceException.java.golden @@ -0,0 +1,33 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import com.example.sdk.runtime.ApiException; +import okhttp3.Headers; + +public final class GetChoiceException extends ApiException { + private final GetChoiceExceptionDetail detail; + private final Headers headers; + private final byte[] rawBody; + + public GetChoiceException(int statusCode, String status, String body, Headers headers, byte[] rawBody, GetChoiceExceptionDetail detail) { + super(statusCode, status, body); + this.headers = headers; + this.rawBody = rawBody.clone(); + this.detail = detail; + } + + public GetChoiceExceptionDetail detail() { + return this.detail; + } + + public Headers headers() { + return this.headers; + } + + public byte[] rawBody() { + return this.rawBody.clone(); + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceExceptionDetail.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceExceptionDetail.java.golden new file mode 100644 index 000000000..b12c43230 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceExceptionDetail.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public sealed interface GetChoiceExceptionDetail permits GetChoiceBadRequest, GetChoiceUnexpected { + int statusCode(); + okhttp3.Headers headers(); + byte[] rawBody(); +} diff --git a/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceResponse.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceResponse.java.golden new file mode 100644 index 000000000..72ed677a2 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceResponse.java.golden @@ -0,0 +1,38 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import okhttp3.Response; + +/** + * GetChoiceResponse carries the response from getChoice. + */ +public class GetChoiceResponse { + private final int statusCode; + private final Response raw; + private final FiniteChoice status200; + + public GetChoiceResponse(int statusCode, Response raw, FiniteChoice status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public FiniteChoice getStatus200() { + return this.status200; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceUnexpected.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceUnexpected.java.golden new file mode 100644 index 000000000..f67d0f3e9 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/GetChoiceUnexpected.java.golden @@ -0,0 +1,22 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public final class GetChoiceUnexpected implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceUnexpected(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + public byte[] body() { return this.rawBody.clone(); } +} diff --git a/tests/golden/java/java-okhttp/union-raw/apis/SelectionApi.java.golden b/tests/golden/java/java-okhttp/union-raw/apis/SelectionApi.java.golden new file mode 100644 index 000000000..ebac5b271 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/apis/SelectionApi.java.golden @@ -0,0 +1,63 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.Request; +import okhttp3.Response; + +/** + * SelectionApi groups operations under the Selection tag. + */ +public class SelectionApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public SelectionApi(ApiClient client) { + this.client = client; + } + + /** + * getChoice GET /choice. + */ + public GetChoiceResponse getChoice() throws IOException { + String path = "/choice"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + okhttp3.ResponseBody responseBody = response.body(); + java.nio.charset.Charset responseCharset = responseBody != null && responseBody.contentType() != null ? responseBody.contentType().charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + byte[] responseBytes = responseBody != null ? responseBody.bytes() : new byte[0]; + String responseText = new String(responseBytes, responseCharset); + GetChoiceExceptionDetail detail; + switch (response.code()) { + case 400: + detail = new GetChoiceBadRequest(response.code(), response.headers(), responseBytes);break; + default: + detail = new GetChoiceUnexpected(response.code(), response.headers(), responseBytes); + } + throw new GetChoiceException(response.code(), response.message(), responseText, response.headers(), responseBytes, detail); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + FiniteChoice status200 = null; + if (response.code() == 200) { + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); + } + return new GetChoiceResponse(response.code(), response, status200); + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/build.gradle.golden b/tests/golden/java/java-okhttp/union-raw/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/AliasChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/AliasChoice.java.golden new file mode 100644 index 000000000..36922ec6e --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/AliasChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AliasChoiceJsonAdapter.class, nullSafe = false) +public sealed interface AliasChoice { + record VariantRaw(JsonElement value0) implements AliasChoice {} +} + +final class AliasChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public AliasChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new AliasChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, AliasChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((AliasChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Alpha.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Alpha.java.golden new file mode 100644 index 000000000..b33314a36 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Alpha.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Alpha { + @SerializedName("first_value") + private String firstValue; + @SerializedName("wire_name") + private String wireName; + + public Alpha(String firstValue, String wireName) { + this.firstValue = firstValue; + this.wireName = wireName; + } + + public String getFirstValue() { + return this.firstValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Ambiguous.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Ambiguous.java.golden new file mode 100644 index 000000000..4b49cd68e --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Ambiguous.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AmbiguousJsonAdapter.class, nullSafe = false) +public sealed interface Ambiguous { + record VariantRaw(JsonElement value0) implements Ambiguous {} +} + +final class AmbiguousJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Ambiguous read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new Ambiguous.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, Ambiguous value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((Ambiguous.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/AmbiguousAlias.java.golden b/tests/golden/java/java-okhttp/union-raw/models/AmbiguousAlias.java.golden new file mode 100644 index 000000000..5cd6ab3b6 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/AmbiguousAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class AmbiguousAlias { + private Ambiguous value; + + public AmbiguousAlias(Ambiguous value) { + this.value = value; + } + + public Ambiguous getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Beta.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Beta.java.golden new file mode 100644 index 000000000..71ed91106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Beta.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Beta { + @SerializedName("second_value") + private String secondValue; + @SerializedName("wire_name") + private String wireName; + + public Beta(String secondValue, String wireName) { + this.secondValue = secondValue; + this.wireName = wireName; + } + + public String getSecondValue() { + return this.secondValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/BinaryChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/BinaryChoice.java.golden new file mode 100644 index 000000000..5ae2448f3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/BinaryChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = BinaryChoiceJsonAdapter.class, nullSafe = false) +public sealed interface BinaryChoice { + record VariantRaw(JsonElement value0) implements BinaryChoice {} +} + +final class BinaryChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public BinaryChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new BinaryChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, BinaryChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((BinaryChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/BranchOne.java.golden b/tests/golden/java/java-okhttp/union-raw/models/BranchOne.java.golden new file mode 100644 index 000000000..e03483640 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/BranchOne.java.golden @@ -0,0 +1,25 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class BranchOne { + private List children; + private int revision; + + public BranchOne(List children, int revision) { + this.children = children; + this.revision = revision; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/BranchTwo.java.golden b/tests/golden/java/java-okhttp/union-raw/models/BranchTwo.java.golden new file mode 100644 index 000000000..3453b6947 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/BranchTwo.java.golden @@ -0,0 +1,34 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class BranchTwo { + private List children; + private int revision; + @SerializedName("second_value") + private String secondValue; + + public BranchTwo(List children, int revision, String secondValue) { + this.children = children; + this.revision = revision; + this.secondValue = secondValue; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ChoiceEnvelope.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ChoiceEnvelope.java.golden new file mode 100644 index 000000000..4f0cd8c61 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ChoiceEnvelope.java.golden @@ -0,0 +1,49 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class ChoiceEnvelope { + @SerializedName("by_name") + private Map byName; + private String caption; + private FiniteChoice choice; + private List choices; + @SerializedName("ordinary_null") + private String ordinaryNull; + + public ChoiceEnvelope(Map byName, String caption, FiniteChoice choice, + List choices, String ordinaryNull) { + this.byName = byName; + this.caption = caption; + this.choice = choice; + this.choices = choices; + this.ordinaryNull = ordinaryNull; + } + + public Map getByName() { + return this.byName; + } + + public String getCaption() { + return this.caption; + } + + public FiniteChoice getChoice() { + return this.choice; + } + + public List getChoices() { + return this.choices; + } + + public String getOrdinaryNull() { + return this.ordinaryNull; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ContainerChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ContainerChoice.java.golden new file mode 100644 index 000000000..7855e97ca --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ContainerChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ContainerChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ContainerChoice { + record VariantRaw(JsonElement value0) implements ContainerChoice {} +} + +final class ContainerChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ContainerChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new ContainerChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, ContainerChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((ContainerChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ContainerOne.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ContainerOne.java.golden new file mode 100644 index 000000000..cba3f808a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ContainerOne.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class ContainerOne { + private List values; + + public ContainerOne(List values) { + this.values = values; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ContainerTwo.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ContainerTwo.java.golden new file mode 100644 index 000000000..29ce4e106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ContainerTwo.java.golden @@ -0,0 +1,28 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class ContainerTwo { + @SerializedName("second_value") + private String secondValue; + private List values; + + public ContainerTwo(String secondValue, List values) { + this.secondValue = secondValue; + this.values = values; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ExcludedNull.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ExcludedNull.java.golden new file mode 100644 index 000000000..d2f8299fe --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ExcludedNull.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class ExcludedNull { + private Integer revision; + + public ExcludedNull(Integer revision) { + this.revision = revision; + } + + public Integer getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/FiniteChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/FiniteChoice.java.golden new file mode 100644 index 000000000..c6931b835 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/FiniteChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FiniteChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FiniteChoice { + record VariantRaw(JsonElement value0) implements FiniteChoice {} +} + +final class FiniteChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FiniteChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new FiniteChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, FiniteChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((FiniteChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/FiniteOne.java.golden b/tests/golden/java/java-okhttp/union-raw/models/FiniteOne.java.golden new file mode 100644 index 000000000..c4b41b025 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/FiniteOne.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteOne { + @SerializedName("first_value") + private String firstValue; + private Map flags; + private int revision; + private List values; + + public FiniteOne(String firstValue, Map flags, int revision, + List values) { + this.firstValue = firstValue; + this.flags = flags; + this.revision = revision; + this.values = values; + } + + public String getFirstValue() { + return this.firstValue; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/FiniteTwo.java.golden b/tests/golden/java/java-okhttp/union-raw/models/FiniteTwo.java.golden new file mode 100644 index 000000000..b918dda08 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/FiniteTwo.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteTwo { + private Map flags; + private int revision; + @SerializedName("second_value") + private String secondValue; + private List values; + + public FiniteTwo(Map flags, int revision, String secondValue, + List values) { + this.flags = flags; + this.revision = revision; + this.secondValue = secondValue; + this.values = values; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/FormatChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/FormatChoice.java.golden new file mode 100644 index 000000000..ab3c8b29e --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/FormatChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FormatChoice { + record VariantRaw(JsonElement value0) implements FormatChoice {} +} + +final class FormatChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new FormatChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, FormatChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((FormatChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/FormatFallback.java.golden b/tests/golden/java/java-okhttp/union-raw/models/FormatFallback.java.golden new file mode 100644 index 000000000..a8b781bb1 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/FormatFallback.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatFallbackJsonAdapter.class, nullSafe = false) +public sealed interface FormatFallback { + record VariantRaw(JsonElement value0) implements FormatFallback {} +} + +final class FormatFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new FormatFallback.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, FormatFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((FormatFallback.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/IncludedNull.java.golden b/tests/golden/java/java-okhttp/union-raw/models/IncludedNull.java.golden new file mode 100644 index 000000000..42b84c965 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/IncludedNull.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class IncludedNull { + private Integer revision; + @SerializedName("second_value") + private String secondValue; + + public IncludedNull(Integer revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public Integer getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/IntegerFallback.java.golden b/tests/golden/java/java-okhttp/union-raw/models/IntegerFallback.java.golden new file mode 100644 index 000000000..5adf4f946 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/IntegerFallback.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = IntegerFallbackJsonAdapter.class, nullSafe = false) +public sealed interface IntegerFallback { + record VariantRaw(JsonElement value0) implements IntegerFallback {} +} + +final class IntegerFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public IntegerFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new IntegerFallback.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, IntegerFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((IntegerFallback.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/LargeChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/LargeChoice.java.golden new file mode 100644 index 000000000..cd3504e4b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/LargeChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = LargeChoiceJsonAdapter.class, nullSafe = false) +public sealed interface LargeChoice { + record VariantRaw(JsonElement value0) implements LargeChoice {} +} + +final class LargeChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public LargeChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new LargeChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, LargeChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((LargeChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/LargeOne.java.golden b/tests/golden/java/java-okhttp/union-raw/models/LargeOne.java.golden new file mode 100644 index 000000000..b48a90e70 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/LargeOne.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class LargeOne { + private long revision; + + public LargeOne(long revision) { + this.revision = revision; + } + + public long getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/LargeTwo.java.golden b/tests/golden/java/java-okhttp/union-raw/models/LargeTwo.java.golden new file mode 100644 index 000000000..4d5431a72 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/LargeTwo.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LargeTwo { + private long revision; + @SerializedName("second_value") + private String secondValue; + + public LargeTwo(long revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public long getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/LeftPart.java.golden b/tests/golden/java/java-okhttp/union-raw/models/LeftPart.java.golden new file mode 100644 index 000000000..bd3898bb7 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/LeftPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LeftPart { + @SerializedName("left_value") + private String leftValue; + + public LeftPart(String leftValue) { + this.leftValue = leftValue; + } + + public String getLeftValue() { + return this.leftValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Narrow.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Narrow.java.golden new file mode 100644 index 000000000..59c5707b8 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Narrow.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class Narrow { + private int identifier; + + public Narrow(int identifier) { + this.identifier = identifier; + } + + public int getIdentifier() { + return this.identifier; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/NullableChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/NullableChoice.java.golden new file mode 100644 index 000000000..0380b345b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/NullableChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableChoiceJsonAdapter.class, nullSafe = false) +public sealed interface NullableChoice { + record VariantRaw(JsonElement value0) implements NullableChoice {} +} + +final class NullableChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new NullableChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, NullableChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((NullableChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/NullableFields.java.golden b/tests/golden/java/java-okhttp/union-raw/models/NullableFields.java.golden new file mode 100644 index 000000000..6beb69d5b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/NullableFields.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableFieldsJsonAdapter.class, nullSafe = false) +public sealed interface NullableFields { + record VariantRaw(JsonElement value0) implements NullableFields {} +} + +final class NullableFieldsJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableFields read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new NullableFields.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, NullableFields value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((NullableFields.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/NumberAlias.java.golden b/tests/golden/java/java-okhttp/union-raw/models/NumberAlias.java.golden new file mode 100644 index 000000000..7aaa9bd2a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/NumberAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class NumberAlias { + private int value; + + public NumberAlias(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/NumericFinite.java.golden b/tests/golden/java/java-okhttp/union-raw/models/NumericFinite.java.golden new file mode 100644 index 000000000..fc116116f --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/NumericFinite.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericFiniteJsonAdapter.class, nullSafe = false) +public sealed interface NumericFinite { + record VariantRaw(JsonElement value0) implements NumericFinite {} +} + +final class NumericFiniteJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericFinite read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new NumericFinite.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, NumericFinite value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((NumericFinite.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/NumericZero.java.golden b/tests/golden/java/java-okhttp/union-raw/models/NumericZero.java.golden new file mode 100644 index 000000000..e5bfc014c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/NumericZero.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericZeroJsonAdapter.class, nullSafe = false) +public sealed interface NumericZero { + record VariantRaw(JsonElement value0) implements NumericZero {} +} + +final class NumericZeroJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericZero read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new NumericZero.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, NumericZero value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((NumericZero.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/OpaqueFirst.java.golden b/tests/golden/java/java-okhttp/union-raw/models/OpaqueFirst.java.golden new file mode 100644 index 000000000..9f783065e --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/OpaqueFirst.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpaqueFirstJsonAdapter.class, nullSafe = false) +public sealed interface OpaqueFirst { + record VariantRaw(JsonElement value0) implements OpaqueFirst {} +} + +final class OpaqueFirstJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpaqueFirst read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new OpaqueFirst.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, OpaqueFirst value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((OpaqueFirst.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/OpenNested.java.golden b/tests/golden/java/java-okhttp/union-raw/models/OpenNested.java.golden new file mode 100644 index 000000000..c22c4fb39 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/OpenNested.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpenNestedJsonAdapter.class, nullSafe = false) +public sealed interface OpenNested { + record VariantRaw(JsonElement value0) implements OpenNested {} +} + +final class OpenNestedJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpenNested read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new OpenNested.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, OpenNested value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((OpenNested.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/RecursiveChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/RecursiveChoice.java.golden new file mode 100644 index 000000000..00cb2116c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/RecursiveChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = RecursiveChoiceJsonAdapter.class, nullSafe = false) +public sealed interface RecursiveChoice { + record VariantRaw(JsonElement value0) implements RecursiveChoice {} +} + +final class RecursiveChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public RecursiveChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new RecursiveChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, RecursiveChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((RecursiveChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/RightPart.java.golden b/tests/golden/java/java-okhttp/union-raw/models/RightPart.java.golden new file mode 100644 index 000000000..4ab795f40 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/RightPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class RightPart { + @SerializedName("right_value") + private int rightValue; + + public RightPart(int rightValue) { + this.rightValue = rightValue; + } + + public int getRightValue() { + return this.rightValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/ScalarChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/ScalarChoice.java.golden new file mode 100644 index 000000000..902c908e6 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/ScalarChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ScalarChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ScalarChoice { + record VariantRaw(JsonElement value0) implements ScalarChoice {} +} + +final class ScalarChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ScalarChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new ScalarChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, ScalarChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((ScalarChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Tree.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Tree.java.golden new file mode 100644 index 000000000..fa02bddf2 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Tree.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = TreeJsonAdapter.class, nullSafe = false) +public sealed interface Tree { + record VariantRaw(JsonElement value0) implements Tree {} +} + +final class TreeJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tree read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new Tree.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, Tree value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((Tree.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/VariantRaw.java.golden b/tests/golden/java/java-okhttp/union-raw/models/VariantRaw.java.golden new file mode 100644 index 000000000..689ea7054 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/VariantRaw.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = VariantRawJsonAdapter.class, nullSafe = false) +public sealed interface VariantRaw { + record VariantRawVariant(JsonElement value0) implements VariantRaw {} +} + +final class VariantRawJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public VariantRaw read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new VariantRaw.VariantRawVariant(element); + } + + @Override + public void write(JsonWriter writer, VariantRaw value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((VariantRaw.VariantRawVariant) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/Wide.java.golden b/tests/golden/java/java-okhttp/union-raw/models/Wide.java.golden new file mode 100644 index 000000000..c64abc094 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/Wide.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Wide { + private long identifier; + @SerializedName("second_value") + private String secondValue; + + public Wide(long identifier, String secondValue) { + this.identifier = identifier; + this.secondValue = secondValue; + } + + public long getIdentifier() { + return this.identifier; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/WrappedChoice.java.golden b/tests/golden/java/java-okhttp/union-raw/models/WrappedChoice.java.golden new file mode 100644 index 000000000..85e44f4d4 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/WrappedChoice.java.golden @@ -0,0 +1,55 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = WrappedChoiceJsonAdapter.class, nullSafe = false) +public sealed interface WrappedChoice { + record VariantRaw(JsonElement value0) implements WrappedChoice {} +} + +final class WrappedChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public WrappedChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + return new WrappedChoice.VariantRaw(element); + } + + @Override + public void write(JsonWriter writer, WrappedChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + JsonElement element = ((WrappedChoice.VariantRaw) value).value0(); + if (element == null) { + element = JsonNull.INSTANCE; + } + + + + boolean serializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(true); + try { + gson.getAdapter(JsonElement.class).write(writer, element); + } + finally { + writer.setSerializeNulls(serializeNulls); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/models/WrappedPair.java.golden b/tests/golden/java/java-okhttp/union-raw/models/WrappedPair.java.golden new file mode 100644 index 000000000..18168e273 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/models/WrappedPair.java.golden @@ -0,0 +1,23 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class WrappedPair { + private LeftPart leftPart; + private RightPart rightPart; + + public WrappedPair(LeftPart leftPart, RightPart rightPart) { + this.leftPart = leftPart; + this.rightPart = rightPart; + } + + public LeftPart getLeftPart() { + return this.leftPart; + } + + public RightPart getRightPart() { + return this.rightPart; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/ApiClient.java.golden new file mode 100644 index 000000000..c45cce839 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/ApiException.java.golden new file mode 100644 index 000000000..b7ed957af --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..3b8992bdc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..242937e90 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/Authenticator.java.golden new file mode 100644 index 000000000..f03b10463 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-raw/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-raw/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..4154f770c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-raw/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/README.md.golden b/tests/golden/java/java-okhttp/union-selection/README.md.golden new file mode 100644 index 000000000..2de4774e9 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/README.md.golden @@ -0,0 +1,7 @@ +# Union selection contract + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `union-selection-contract`. diff --git a/tests/golden/java/java-okhttp/union-selection/build.gradle.golden b/tests/golden/java/java-okhttp/union-selection/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/Choice.java.golden b/tests/golden/java/java-okhttp/union-selection/models/Choice.java.golden new file mode 100644 index 000000000..05afb4397 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/Choice.java.golden @@ -0,0 +1,155 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ChoiceJsonAdapter.class, nullSafe = false) +public sealed interface Choice { + record VariantNumericOne(NumericOne value0) implements Choice {} + + record VariantNumericTwo(NumericTwo value0) implements Choice {} +} + +final class ChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Choice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches5(element)) { + selected = 1; + } + if (selected == 0) { + return new Choice.VariantNumericOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Choice.VariantNumericTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Choice union value"); + } + + @Override + public void write(JsonWriter writer, Choice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Choice.VariantNumericOne) { + gson.toJson(gson.toJsonTree(((Choice.VariantNumericOne) value).value0()), writer); + return; + } + if (value instanceof Choice.VariantNumericTwo) { + gson.toJson(gson.toJsonTree(((Choice.VariantNumericTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("revision") || !matches2(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches6(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/ClientTool.java.golden b/tests/golden/java/java-okhttp/union-selection/models/ClientTool.java.golden new file mode 100644 index 000000000..7f4e5afba --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/ClientTool.java.golden @@ -0,0 +1,28 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class ClientTool { + @SerializedName("input_schema") + private Map inputSchema; + private String name; + + public ClientTool(Map inputSchema, String name) { + this.inputSchema = inputSchema; + this.name = name; + } + + public Map getInputSchema() { + return this.inputSchema; + } + + public String getName() { + return this.name; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/Envelope.java.golden b/tests/golden/java/java-okhttp/union-selection/models/Envelope.java.golden new file mode 100644 index 000000000..b4063f3f0 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/Envelope.java.golden @@ -0,0 +1,36 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class Envelope { + private Choice choice; + @SerializedName("choice_list") + private List choiceList; + @SerializedName("choice_map") + private Map choiceMap; + + public Envelope(Choice choice, List choiceList, Map choiceMap) { + this.choice = choice; + this.choiceList = choiceList; + this.choiceMap = choiceMap; + } + + public Choice getChoice() { + return this.choice; + } + + public List getChoiceList() { + return this.choiceList; + } + + public Map getChoiceMap() { + return this.choiceMap; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/NumericOne.java.golden b/tests/golden/java/java-okhttp/union-selection/models/NumericOne.java.golden new file mode 100644 index 000000000..8b5257554 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/NumericOne.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class NumericOne { + @SerializedName("first_value") + private String firstValue; + private int revision; + + public NumericOne(String firstValue, int revision) { + this.firstValue = firstValue; + this.revision = revision; + } + + public String getFirstValue() { + return this.firstValue; + } + + public int getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/NumericTwo.java.golden b/tests/golden/java/java-okhttp/union-selection/models/NumericTwo.java.golden new file mode 100644 index 000000000..960ca40bc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/NumericTwo.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class NumericTwo { + private int revision; + @SerializedName("second_value") + private String secondValue; + + public NumericTwo(int revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/Request.java.golden b/tests/golden/java/java-okhttp/union-selection/models/Request.java.golden new file mode 100644 index 000000000..c11025295 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/Request.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class Request { + private List tools; + + public Request(List tools) { + this.tools = tools; + } + + public List getTools() { + return this.tools; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/RequiredChoice.java.golden b/tests/golden/java/java-okhttp/union-selection/models/RequiredChoice.java.golden new file mode 100644 index 000000000..c89208d85 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/RequiredChoice.java.golden @@ -0,0 +1,155 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = RequiredChoiceJsonAdapter.class, nullSafe = false) +public sealed interface RequiredChoice { + record VariantRequiredOne(RequiredOne value0) implements RequiredChoice {} + + record VariantRequiredTwo(RequiredTwo value0) implements RequiredChoice {} +} + +final class RequiredChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public RequiredChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches5(element)) { + selected = 1; + } + if (selected == 0) { + return new RequiredChoice.VariantRequiredOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new RequiredChoice.VariantRequiredTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown RequiredChoice union value"); + } + + @Override + public void write(JsonWriter writer, RequiredChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof RequiredChoice.VariantRequiredOne) { + gson.toJson(gson.toJsonTree(((RequiredChoice.VariantRequiredOne) value).value0()), writer); + return; + } + if (value instanceof RequiredChoice.VariantRequiredTwo) { + gson.toJson(gson.toJsonTree(((RequiredChoice.VariantRequiredTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("first_value") || !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("revision") || !matches2(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches6(object.get("revision"))) { + return false; + } + if (!object.has("second_value") || !matches1(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/RequiredOne.java.golden b/tests/golden/java/java-okhttp/union-selection/models/RequiredOne.java.golden new file mode 100644 index 000000000..b1e1c2fa3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/RequiredOne.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class RequiredOne { + @SerializedName("first_value") + private String firstValue; + private int revision; + + public RequiredOne(String firstValue, int revision) { + this.firstValue = firstValue; + this.revision = revision; + } + + public String getFirstValue() { + return this.firstValue; + } + + public int getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/RequiredTwo.java.golden b/tests/golden/java/java-okhttp/union-selection/models/RequiredTwo.java.golden new file mode 100644 index 000000000..b3a81503b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/RequiredTwo.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class RequiredTwo { + private int revision; + @SerializedName("second_value") + private String secondValue; + + public RequiredTwo(int revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/SearchTool.java.golden b/tests/golden/java/java-okhttp/union-selection/models/SearchTool.java.golden new file mode 100644 index 000000000..e7846a7d9 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/SearchTool.java.golden @@ -0,0 +1,32 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class SearchTool { + @SerializedName("max_uses") + private Integer maxUses; + private String name; + private String type; + + public SearchTool(Integer maxUses, String name, String type) { + this.maxUses = maxUses; + this.name = name; + this.type = type; + } + + public Integer getMaxUses() { + return this.maxUses; + } + + public String getName() { + return this.name; + } + + public String getType() { + return this.type; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/models/Tool.java.golden b/tests/golden/java/java-okhttp/union-selection/models/Tool.java.golden new file mode 100644 index 000000000..4c12d23d1 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/models/Tool.java.golden @@ -0,0 +1,140 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ToolJsonAdapter.class, nullSafe = false) +public sealed interface Tool { + record VariantClientTool(ClientTool value0) implements Tool {} + + record VariantSearchTool(SearchTool value0) implements Tool {} +} + +final class ToolJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tool read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (selected < 0 && matches0(element)) { + selected = 0; + } + if (selected < 0 && matches3(element)) { + selected = 1; + } + if (selected == 0) { + return new Tool.VariantClientTool(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Tool.VariantSearchTool(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Tool union value"); + } + + @Override + public void write(JsonWriter writer, Tool value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Tool.VariantClientTool) { + gson.toJson(gson.toJsonTree(((Tool.VariantClientTool) value).value0()), writer); + return; + } + if (value instanceof Tool.VariantSearchTool) { + gson.toJson(gson.toJsonTree(((Tool.VariantSearchTool) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("input_schema") || !matches1(object.get("input_schema"))) { + return false; + } + if (!object.has("name") || !matches2(object.get("name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!true) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("max_uses") && !matches4(object.get("max_uses"))) { + return false; + } + if (!object.has("name") || !matches5(object.get("name"))) { + return false; + } + if (!object.has("type") || !matches6(object.get("type"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("web_search"); + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString() && candidate.getAsString().equals("web_search_20250305"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/ApiClient.java.golden new file mode 100644 index 000000000..f40888f19 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/ApiException.java.golden new file mode 100644 index 000000000..555f6541b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..1597f4dc7 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..a865b9310 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/Authenticator.java.golden new file mode 100644 index 000000000..0bc445a81 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-selection/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-selection/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..b7d2424bd --- /dev/null +++ b/tests/golden/java/java-okhttp/union-selection/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/README.md.golden b/tests/golden/java/java-okhttp/union-strict/README.md.golden new file mode 100644 index 000000000..7dfcb9f54 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/README.md.golden @@ -0,0 +1,7 @@ +# Java union policies + +Generated Java SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `java-union-policies`. diff --git a/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceBadRequest.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceBadRequest.java.golden new file mode 100644 index 000000000..98bbfc562 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceBadRequest.java.golden @@ -0,0 +1,40 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +public final class GetChoiceBadRequest implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceBadRequest(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + + private String textBody() { + String contentTypeHeader = this.headers.get("Content-Type"); + okhttp3.MediaType contentType = contentTypeHeader != null ? okhttp3.MediaType.parse(contentTypeHeader) : null; + java.nio.charset.Charset charset = contentType != null ? contentType.charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + return new String(this.rawBody, charset); + } + public FiniteChoice body() { + String text = textBody(); + return new Gson().fromJson(text.isEmpty() ? "null" : text, new TypeToken() {}.getType()); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceException.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceException.java.golden new file mode 100644 index 000000000..350e4f2f3 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceException.java.golden @@ -0,0 +1,33 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import com.example.sdk.runtime.ApiException; +import okhttp3.Headers; + +public final class GetChoiceException extends ApiException { + private final GetChoiceExceptionDetail detail; + private final Headers headers; + private final byte[] rawBody; + + public GetChoiceException(int statusCode, String status, String body, Headers headers, byte[] rawBody, GetChoiceExceptionDetail detail) { + super(statusCode, status, body); + this.headers = headers; + this.rawBody = rawBody.clone(); + this.detail = detail; + } + + public GetChoiceExceptionDetail detail() { + return this.detail; + } + + public Headers headers() { + return this.headers; + } + + public byte[] rawBody() { + return this.rawBody.clone(); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceExceptionDetail.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceExceptionDetail.java.golden new file mode 100644 index 000000000..b12c43230 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceExceptionDetail.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public sealed interface GetChoiceExceptionDetail permits GetChoiceBadRequest, GetChoiceUnexpected { + int statusCode(); + okhttp3.Headers headers(); + byte[] rawBody(); +} diff --git a/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceResponse.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceResponse.java.golden new file mode 100644 index 000000000..72ed677a2 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceResponse.java.golden @@ -0,0 +1,38 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.util.List; +import java.util.Map; + +import com.example.sdk.models.*; +import okhttp3.Response; + +/** + * GetChoiceResponse carries the response from getChoice. + */ +public class GetChoiceResponse { + private final int statusCode; + private final Response raw; + private final FiniteChoice status200; + + public GetChoiceResponse(int statusCode, Response raw, FiniteChoice status200) { + this.statusCode = statusCode; + this.raw = raw; + this.status200 = status200; + } + + public int getStatusCode() { + return this.statusCode; + } + + public Response getRaw() { + return this.raw; + } + + public FiniteChoice getStatus200() { + return this.status200; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceUnexpected.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceUnexpected.java.golden new file mode 100644 index 000000000..f67d0f3e9 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/GetChoiceUnexpected.java.golden @@ -0,0 +1,22 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +public final class GetChoiceUnexpected implements GetChoiceExceptionDetail { + private final int statusCode; + private final okhttp3.Headers headers; + private final byte[] rawBody; + + public GetChoiceUnexpected(int statusCode, okhttp3.Headers headers, byte[] rawBody) { + this.statusCode = statusCode; + this.headers = headers; + this.rawBody = rawBody.clone(); + } + + public int statusCode() { return this.statusCode; } + public okhttp3.Headers headers() { return this.headers; } + public byte[] rawBody() { return this.rawBody.clone(); } + public byte[] body() { return this.rawBody.clone(); } +} diff --git a/tests/golden/java/java-okhttp/union-strict/apis/SelectionApi.java.golden b/tests/golden/java/java-okhttp/union-strict/apis/SelectionApi.java.golden new file mode 100644 index 000000000..ebac5b271 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/apis/SelectionApi.java.golden @@ -0,0 +1,63 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.apis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.example.sdk.models.*; +import com.example.sdk.runtime.ApiClient; +import com.example.sdk.runtime.ApiException; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import okhttp3.Request; +import okhttp3.Response; + +/** + * SelectionApi groups operations under the Selection tag. + */ +public class SelectionApi { + private final ApiClient client; + private final Gson gson = new Gson(); + + public SelectionApi(ApiClient client) { + this.client = client; + } + + /** + * getChoice GET /choice. + */ + public GetChoiceResponse getChoice() throws IOException { + String path = "/choice"; + Request request = client.newRequest("GET", path, null, null); + Response response = client.execute(request); + + if (!response.isSuccessful()) { + okhttp3.ResponseBody responseBody = response.body(); + java.nio.charset.Charset responseCharset = responseBody != null && responseBody.contentType() != null ? responseBody.contentType().charset(StandardCharsets.UTF_8) : StandardCharsets.UTF_8; + byte[] responseBytes = responseBody != null ? responseBody.bytes() : new byte[0]; + String responseText = new String(responseBytes, responseCharset); + GetChoiceExceptionDetail detail; + switch (response.code()) { + case 400: + detail = new GetChoiceBadRequest(response.code(), response.headers(), responseBytes);break; + default: + detail = new GetChoiceUnexpected(response.code(), response.headers(), responseBytes); + } + throw new GetChoiceException(response.code(), response.message(), responseText, response.headers(), responseBytes, detail); + } + byte[] responseBytes = response.body() != null ? response.body().bytes() : new byte[0]; + String responseText = new String(responseBytes, StandardCharsets.UTF_8); + FiniteChoice status200 = null; + if (response.code() == 200) { + status200 = gson.fromJson(responseText.isEmpty() ? "null" : responseText, new TypeToken() {}.getType()); + } + return new GetChoiceResponse(response.code(), response, status200); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/build.gradle.golden b/tests/golden/java/java-okhttp/union-strict/build.gradle.golden new file mode 100644 index 000000000..4b3a3bddf --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/build.gradle.golden @@ -0,0 +1,29 @@ +plugins { + id("java-library") +} + +group = "com.example.sdk" +version = "1.0.0" + +repositories { + mavenCentral() +} + +dependencies { + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.google.code.gson:gson:2.11.0") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +sourceSets { + main { + java { + srcDirs("apis", "models", "runtime") + } + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/AliasChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/AliasChoice.java.golden new file mode 100644 index 000000000..5a598d070 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/AliasChoice.java.golden @@ -0,0 +1,120 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AliasChoiceJsonAdapter.class, nullSafe = false) +public sealed interface AliasChoice { + record VariantNumberAlias(NumberAlias value0) implements AliasChoice {} + + record VariantAlpha(Alpha value0) implements AliasChoice {} +} + +final class AliasChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public AliasChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union AliasChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches2(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union AliasChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new AliasChoice.VariantNumberAlias(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new AliasChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown AliasChoice union value"); + } + + @Override + public void write(JsonWriter writer, AliasChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof AliasChoice.VariantNumberAlias) { + gson.toJson(gson.toJsonTree(((AliasChoice.VariantNumberAlias) value).value0()), writer); + return; + } + if (value instanceof AliasChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((AliasChoice.VariantAlpha) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("value") || !matches1(object.get("value"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches3(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Alpha.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Alpha.java.golden new file mode 100644 index 000000000..b33314a36 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Alpha.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Alpha { + @SerializedName("first_value") + private String firstValue; + @SerializedName("wire_name") + private String wireName; + + public Alpha(String firstValue, String wireName) { + this.firstValue = firstValue; + this.wireName = wireName; + } + + public String getFirstValue() { + return this.firstValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Ambiguous.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Ambiguous.java.golden new file mode 100644 index 000000000..032a9f546 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Ambiguous.java.golden @@ -0,0 +1,104 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = AmbiguousJsonAdapter.class, nullSafe = false) +public sealed interface Ambiguous { + record VariantAlpha(Alpha value0) implements Ambiguous {} + + record VariantBeta(Beta value0) implements Ambiguous {} +} + +final class AmbiguousJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Ambiguous read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Ambiguous: multiple concrete members match"); + } + selected = 0; + } + if (matches2(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Ambiguous: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new Ambiguous.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Ambiguous.VariantBeta(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Ambiguous union value"); + } + + @Override + public void write(JsonWriter writer, Ambiguous value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Ambiguous.VariantAlpha) { + gson.toJson(gson.toJsonTree(((Ambiguous.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof Ambiguous.VariantBeta) { + gson.toJson(gson.toJsonTree(((Ambiguous.VariantBeta) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/AmbiguousAlias.java.golden b/tests/golden/java/java-okhttp/union-strict/models/AmbiguousAlias.java.golden new file mode 100644 index 000000000..5cd6ab3b6 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/AmbiguousAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class AmbiguousAlias { + private Ambiguous value; + + public AmbiguousAlias(Ambiguous value) { + this.value = value; + } + + public Ambiguous getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Beta.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Beta.java.golden new file mode 100644 index 000000000..71ed91106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Beta.java.golden @@ -0,0 +1,27 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Beta { + @SerializedName("second_value") + private String secondValue; + @SerializedName("wire_name") + private String wireName; + + public Beta(String secondValue, String wireName) { + this.secondValue = secondValue; + this.wireName = wireName; + } + + public String getSecondValue() { + return this.secondValue; + } + + public String getWireName() { + return this.wireName; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/BinaryChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/BinaryChoice.java.golden new file mode 100644 index 000000000..082244ddc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/BinaryChoice.java.golden @@ -0,0 +1,112 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = BinaryChoiceJsonAdapter.class, nullSafe = false) +public sealed interface BinaryChoice { + record VariantBinary(byte[] value0) implements BinaryChoice {} + + record VariantString(String value0) implements BinaryChoice {} +} + +final class BinaryChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public BinaryChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union BinaryChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches1(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union BinaryChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new BinaryChoice.VariantBinary(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new BinaryChoice.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown BinaryChoice union value"); + } + + @Override + public void write(JsonWriter writer, BinaryChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof BinaryChoice.VariantBinary) { + gson.toJson(gson.toJsonTree(((BinaryChoice.VariantBinary) value).value0()), writer); + return; + } + if (value instanceof BinaryChoice.VariantString) { + gson.toJson(gson.toJsonTree(((BinaryChoice.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!item.isJsonPrimitive() || !item.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = item.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + if (significand.signum() == 0) { + continue; + } + java.math.BigInteger scale = java.math.BigInteger.valueOf(significand.scale()).subtract(numberExponent(literal)); + + if (scale.signum() > 0 || scale.compareTo(java.math.BigInteger.valueOf(-2)) < 0) { + return false; + } + java.math.BigDecimal number = new java.math.BigDecimal(significand.unscaledValue(), scale.intValueExact()); + if (number.compareTo(new java.math.BigDecimal("-128")) < 0 || number.compareTo(new java.math.BigDecimal("255")) > 0) { + return false; + } + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/BranchOne.java.golden b/tests/golden/java/java-okhttp/union-strict/models/BranchOne.java.golden new file mode 100644 index 000000000..e03483640 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/BranchOne.java.golden @@ -0,0 +1,25 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class BranchOne { + private List children; + private int revision; + + public BranchOne(List children, int revision) { + this.children = children; + this.revision = revision; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/BranchTwo.java.golden b/tests/golden/java/java-okhttp/union-strict/models/BranchTwo.java.golden new file mode 100644 index 000000000..3453b6947 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/BranchTwo.java.golden @@ -0,0 +1,34 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class BranchTwo { + private List children; + private int revision; + @SerializedName("second_value") + private String secondValue; + + public BranchTwo(List children, int revision, String secondValue) { + this.children = children; + this.revision = revision; + this.secondValue = secondValue; + } + + public List getChildren() { + return this.children; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ChoiceEnvelope.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ChoiceEnvelope.java.golden new file mode 100644 index 000000000..4f0cd8c61 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ChoiceEnvelope.java.golden @@ -0,0 +1,49 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class ChoiceEnvelope { + @SerializedName("by_name") + private Map byName; + private String caption; + private FiniteChoice choice; + private List choices; + @SerializedName("ordinary_null") + private String ordinaryNull; + + public ChoiceEnvelope(Map byName, String caption, FiniteChoice choice, + List choices, String ordinaryNull) { + this.byName = byName; + this.caption = caption; + this.choice = choice; + this.choices = choices; + this.ordinaryNull = ordinaryNull; + } + + public Map getByName() { + return this.byName; + } + + public String getCaption() { + return this.caption; + } + + public FiniteChoice getChoice() { + return this.choice; + } + + public List getChoices() { + return this.choices; + } + + public String getOrdinaryNull() { + return this.ordinaryNull; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ContainerChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ContainerChoice.java.golden new file mode 100644 index 000000000..714feca7e --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ContainerChoice.java.golden @@ -0,0 +1,182 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ContainerChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ContainerChoice { + record VariantContainerOne(ContainerOne value0) implements ContainerChoice {} + + record VariantContainerTwo(ContainerTwo value0) implements ContainerChoice {} +} + +final class ContainerChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ContainerChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union ContainerChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches5(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union ContainerChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new ContainerChoice.VariantContainerOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new ContainerChoice.VariantContainerTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown ContainerChoice union value"); + } + + @Override + public void write(JsonWriter writer, ContainerChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof ContainerChoice.VariantContainerOne) { + gson.toJson(gson.toJsonTree(((ContainerChoice.VariantContainerOne) value).value0()), writer); + return; + } + if (value instanceof ContainerChoice.VariantContainerTwo) { + gson.toJson(gson.toJsonTree(((ContainerChoice.VariantContainerTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("values") || !matches1(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches6(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches7(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches8(item)) { + return false; + } + } + return true; + } + + private static boolean matches8(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches9(candidate)) { + return false; + } + return true; + } + + private static boolean matches9(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ContainerOne.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ContainerOne.java.golden new file mode 100644 index 000000000..cba3f808a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ContainerOne.java.golden @@ -0,0 +1,19 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +public class ContainerOne { + private List values; + + public ContainerOne(List values) { + this.values = values; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ContainerTwo.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ContainerTwo.java.golden new file mode 100644 index 000000000..29ce4e106 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ContainerTwo.java.golden @@ -0,0 +1,28 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +public class ContainerTwo { + @SerializedName("second_value") + private String secondValue; + private List values; + + public ContainerTwo(String secondValue, List values) { + this.secondValue = secondValue; + this.values = values; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ExcludedNull.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ExcludedNull.java.golden new file mode 100644 index 000000000..d2f8299fe --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ExcludedNull.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class ExcludedNull { + private Integer revision; + + public ExcludedNull(Integer revision) { + this.revision = revision; + } + + public Integer getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/FiniteChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/FiniteChoice.java.golden new file mode 100644 index 000000000..4a6052275 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/FiniteChoice.java.golden @@ -0,0 +1,305 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FiniteChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FiniteChoice { + record VariantFiniteOne(FiniteOne value0) implements FiniteChoice {} + + record VariantFiniteTwo(FiniteTwo value0) implements FiniteChoice {} +} + +final class FiniteChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FiniteChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union FiniteChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches14(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union FiniteChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new FiniteChoice.VariantFiniteOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FiniteChoice.VariantFiniteTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FiniteChoice union value"); + } + + @Override + public void write(JsonWriter writer, FiniteChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FiniteChoice.VariantFiniteOne) { + gson.toJson(gson.toJsonTree(((FiniteChoice.VariantFiniteOne) value).value0()), writer); + return; + } + if (value instanceof FiniteChoice.VariantFiniteTwo) { + gson.toJson(gson.toJsonTree(((FiniteChoice.VariantFiniteTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("flags") || !matches2(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches6(object.get("revision"))) { + return false; + } + if (!object.has("values") || !matches9(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches3(item)) { + return false; + } + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == false; + } + + private static boolean matches6(JsonElement candidate) { + if (!matches7(candidate)) { + return false; + } + if (!matches8(candidate)) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches9(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches10(item)) { + return false; + } + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches12(candidate)) { + return false; + } + return true; + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches12(JsonElement candidate) { + if (matches8(candidate)) { + return true; + } + if (matches13(candidate)) { + return true; + } + return false; + } + + private static boolean matches13(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1.5"); + } + + private static boolean matches14(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("flags") || !matches15(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches18(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches20(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches15(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches16(item)) { + return false; + } + } + return true; + } + + private static boolean matches16(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches17(candidate)) { + return false; + } + return true; + } + + private static boolean matches17(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches18(JsonElement candidate) { + if (!matches7(candidate)) { + return false; + } + if (!matches19(candidate)) { + return false; + } + return true; + } + + private static boolean matches19(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches20(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches21(item)) { + return false; + } + } + return true; + } + + private static boolean matches21(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches22(candidate)) { + return false; + } + return true; + } + + private static boolean matches22(JsonElement candidate) { + if (matches19(candidate)) { + return true; + } + if (matches23(candidate)) { + return true; + } + return false; + } + + private static boolean matches23(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/FiniteOne.java.golden b/tests/golden/java/java-okhttp/union-strict/models/FiniteOne.java.golden new file mode 100644 index 000000000..c4b41b025 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/FiniteOne.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteOne { + @SerializedName("first_value") + private String firstValue; + private Map flags; + private int revision; + private List values; + + public FiniteOne(String firstValue, Map flags, int revision, + List values) { + this.firstValue = firstValue; + this.flags = flags; + this.revision = revision; + this.values = values; + } + + public String getFirstValue() { + return this.firstValue; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/FiniteTwo.java.golden b/tests/golden/java/java-okhttp/union-strict/models/FiniteTwo.java.golden new file mode 100644 index 000000000..b918dda08 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/FiniteTwo.java.golden @@ -0,0 +1,42 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import java.util.List; +import java.util.Map; + +import com.google.gson.annotations.SerializedName; + +public class FiniteTwo { + private Map flags; + private int revision; + @SerializedName("second_value") + private String secondValue; + private List values; + + public FiniteTwo(Map flags, int revision, String secondValue, + List values) { + this.flags = flags; + this.revision = revision; + this.secondValue = secondValue; + this.values = values; + } + + public Map getFlags() { + return this.flags; + } + + public int getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } + + public List getValues() { + return this.values; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/FormatChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/FormatChoice.java.golden new file mode 100644 index 000000000..e25511cc8 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/FormatChoice.java.golden @@ -0,0 +1,120 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatChoiceJsonAdapter.class, nullSafe = false) +public sealed interface FormatChoice { + record VariantNarrow(Narrow value0) implements FormatChoice {} + + record VariantWide(Wide value0) implements FormatChoice {} +} + +final class FormatChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union FormatChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches2(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union FormatChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new FormatChoice.VariantNarrow(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FormatChoice.VariantWide(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FormatChoice union value"); + } + + @Override + public void write(JsonWriter writer, FormatChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FormatChoice.VariantNarrow) { + gson.toJson(gson.toJsonTree(((FormatChoice.VariantNarrow) value).value0()), writer); + return; + } + if (value instanceof FormatChoice.VariantWide) { + gson.toJson(gson.toJsonTree(((FormatChoice.VariantWide) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + if (object.has("second_value") && !matches3(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/FormatFallback.java.golden b/tests/golden/java/java-okhttp/union-strict/models/FormatFallback.java.golden new file mode 100644 index 000000000..7fd0a7756 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/FormatFallback.java.golden @@ -0,0 +1,99 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = FormatFallbackJsonAdapter.class, nullSafe = false) +public sealed interface FormatFallback { + record VariantNarrow(Narrow value0) implements FormatFallback {} + + record VariantAny(JsonElement value0) implements FormatFallback {} +} + +final class FormatFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public FormatFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union FormatFallback: multiple concrete members match"); + } + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new FormatFallback.VariantNarrow(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new FormatFallback.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown FormatFallback union value"); + } + + @Override + public void write(JsonWriter writer, FormatFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof FormatFallback.VariantNarrow) { + gson.toJson(gson.toJsonTree(((FormatFallback.VariantNarrow) value).value0()), writer); + return; + } + if (value instanceof FormatFallback.VariantAny) { + gson.toJson(gson.toJsonTree(((FormatFallback.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("identifier") || !matches1(object.get("identifier"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/IncludedNull.java.golden b/tests/golden/java/java-okhttp/union-strict/models/IncludedNull.java.golden new file mode 100644 index 000000000..42b84c965 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/IncludedNull.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class IncludedNull { + private Integer revision; + @SerializedName("second_value") + private String secondValue; + + public IncludedNull(Integer revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public Integer getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/IntegerFallback.java.golden b/tests/golden/java/java-okhttp/union-strict/models/IntegerFallback.java.golden new file mode 100644 index 000000000..b381348ff --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/IntegerFallback.java.golden @@ -0,0 +1,88 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = IntegerFallbackJsonAdapter.class, nullSafe = false) +public sealed interface IntegerFallback { + record VariantInteger(int value0) implements IntegerFallback {} + + record VariantAny(JsonElement value0) implements IntegerFallback {} +} + +final class IntegerFallbackJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public IntegerFallback read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union IntegerFallback: multiple concrete members match"); + } + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new IntegerFallback.VariantInteger(gson.fromJson(element, Integer.class)); + } + if (selected == 1) { + return new IntegerFallback.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown IntegerFallback union value"); + } + + @Override + public void write(JsonWriter writer, IntegerFallback value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof IntegerFallback.VariantInteger) { + gson.toJson(gson.toJsonTree(((IntegerFallback.VariantInteger) value).value0()), writer); + return; + } + if (value instanceof IntegerFallback.VariantAny) { + gson.toJson(gson.toJsonTree(((IntegerFallback.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/LargeChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/LargeChoice.java.golden new file mode 100644 index 000000000..c07ead8d0 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/LargeChoice.java.golden @@ -0,0 +1,158 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = LargeChoiceJsonAdapter.class, nullSafe = false) +public sealed interface LargeChoice { + record VariantLargeOne(LargeOne value0) implements LargeChoice {} + + record VariantLargeTwo(LargeTwo value0) implements LargeChoice {} +} + +final class LargeChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public LargeChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union LargeChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches4(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union LargeChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new LargeChoice.VariantLargeOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new LargeChoice.VariantLargeTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown LargeChoice union value"); + } + + @Override + public void write(JsonWriter writer, LargeChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof LargeChoice.VariantLargeOne) { + gson.toJson(gson.toJsonTree(((LargeChoice.VariantLargeOne) value).value0()), writer); + return; + } + if (value instanceof LargeChoice.VariantLargeTwo) { + gson.toJson(gson.toJsonTree(((LargeChoice.VariantLargeTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches1(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "9007199254740992"); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches7(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "9007199254740993"); + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/LargeOne.java.golden b/tests/golden/java/java-okhttp/union-strict/models/LargeOne.java.golden new file mode 100644 index 000000000..b48a90e70 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/LargeOne.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class LargeOne { + private long revision; + + public LargeOne(long revision) { + this.revision = revision; + } + + public long getRevision() { + return this.revision; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/LargeTwo.java.golden b/tests/golden/java/java-okhttp/union-strict/models/LargeTwo.java.golden new file mode 100644 index 000000000..4d5431a72 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/LargeTwo.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LargeTwo { + private long revision; + @SerializedName("second_value") + private String secondValue; + + public LargeTwo(long revision, String secondValue) { + this.revision = revision; + this.secondValue = secondValue; + } + + public long getRevision() { + return this.revision; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/LeftPart.java.golden b/tests/golden/java/java-okhttp/union-strict/models/LeftPart.java.golden new file mode 100644 index 000000000..bd3898bb7 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/LeftPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class LeftPart { + @SerializedName("left_value") + private String leftValue; + + public LeftPart(String leftValue) { + this.leftValue = leftValue; + } + + public String getLeftValue() { + return this.leftValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Narrow.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Narrow.java.golden new file mode 100644 index 000000000..59c5707b8 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Narrow.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class Narrow { + private int identifier; + + public Narrow(int identifier) { + this.identifier = identifier; + } + + public int getIdentifier() { + return this.identifier; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/NullableChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/NullableChoice.java.golden new file mode 100644 index 000000000..c2668d937 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/NullableChoice.java.golden @@ -0,0 +1,84 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableChoiceJsonAdapter.class, nullSafe = false) +public sealed interface NullableChoice { + record VariantAlpha(Alpha value0) implements NullableChoice {} + + /** + * The union's null value. + */ + enum VariantNull implements NullableChoice { INSTANCE } +} + +final class NullableChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return NullableChoice.VariantNull.INSTANCE; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union NullableChoice: multiple concrete members match"); + } + selected = 0; + } + if (selected == 0) { + return new NullableChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NullableChoice union value"); + } + + @Override + public void write(JsonWriter writer, NullableChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NullableChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((NullableChoice.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof NullableChoice.VariantNull) { + writer.nullValue(); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/NullableFields.java.golden b/tests/golden/java/java-okhttp/union-strict/models/NullableFields.java.golden new file mode 100644 index 000000000..7e23cb45c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/NullableFields.java.golden @@ -0,0 +1,190 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NullableFieldsJsonAdapter.class, nullSafe = false) +public sealed interface NullableFields { + record VariantExcludedNull(ExcludedNull value0) implements NullableFields {} + + record VariantIncludedNull(IncludedNull value0) implements NullableFields {} +} + +final class NullableFieldsJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NullableFields read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union NullableFields: multiple concrete members match"); + } + selected = 0; + } + if (matches6(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union NullableFields: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new NullableFields.VariantExcludedNull(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new NullableFields.VariantIncludedNull(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NullableFields union value"); + } + + @Override + public void write(JsonWriter writer, NullableFields value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NullableFields.VariantExcludedNull) { + gson.toJson(gson.toJsonTree(((NullableFields.VariantExcludedNull) value).value0()), writer); + return; + } + if (value instanceof NullableFields.VariantIncludedNull) { + gson.toJson(gson.toJsonTree(((NullableFields.VariantIncludedNull) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches1(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!matches2(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonNull() || matches3(candidate); + } + + private static boolean matches3(JsonElement candidate) { + if (!matches4(candidate)) { + return false; + } + if (!matches5(candidate)) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("revision") || !matches7(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches12(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches7(JsonElement candidate) { + if (!matches8(candidate)) { + return false; + } + if (!matches9(candidate)) { + return false; + } + return true; + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonNull() || matches4(candidate); + } + + private static boolean matches9(JsonElement candidate) { + if (matches10(candidate)) { + return true; + } + if (matches11(candidate)) { + return true; + } + return false; + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonNull(); + } + + private static boolean matches12(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/NumberAlias.java.golden b/tests/golden/java/java-okhttp/union-strict/models/NumberAlias.java.golden new file mode 100644 index 000000000..7aaa9bd2a --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/NumberAlias.java.golden @@ -0,0 +1,17 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class NumberAlias { + private int value; + + public NumberAlias(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/NumericFinite.java.golden b/tests/golden/java/java-okhttp/union-strict/models/NumericFinite.java.golden new file mode 100644 index 000000000..c6306b181 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/NumericFinite.java.golden @@ -0,0 +1,120 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericFiniteJsonAdapter.class, nullSafe = false) +public sealed interface NumericFinite { + record VariantNumber(double value0) implements NumericFinite {} + + record VariantAny(JsonElement value0) implements NumericFinite {} +} + +final class NumericFiniteJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericFinite read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union NumericFinite: multiple concrete members match"); + } + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new NumericFinite.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 1) { + return new NumericFinite.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NumericFinite union value"); + } + + @Override + public void write(JsonWriter writer, NumericFinite value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NumericFinite.VariantNumber) { + gson.toJson(gson.toJsonTree(((NumericFinite.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof NumericFinite.VariantAny) { + gson.toJson(gson.toJsonTree(((NumericFinite.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1.25"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/NumericZero.java.golden b/tests/golden/java/java-okhttp/union-strict/models/NumericZero.java.golden new file mode 100644 index 000000000..175f00c9c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/NumericZero.java.golden @@ -0,0 +1,120 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = NumericZeroJsonAdapter.class, nullSafe = false) +public sealed interface NumericZero { + record VariantNumber(double value0) implements NumericZero {} + + record VariantAny(JsonElement value0) implements NumericZero {} +} + +final class NumericZeroJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public NumericZero read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union NumericZero: multiple concrete members match"); + } + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new NumericZero.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 1) { + return new NumericZero.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown NumericZero union value"); + } + + @Override + public void write(JsonWriter writer, NumericZero value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof NumericZero.VariantNumber) { + gson.toJson(gson.toJsonTree(((NumericZero.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof NumericZero.VariantAny) { + gson.toJson(gson.toJsonTree(((NumericZero.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "0"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/OpaqueFirst.java.golden b/tests/golden/java/java-okhttp/union-strict/models/OpaqueFirst.java.golden new file mode 100644 index 000000000..c52f1a22b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/OpaqueFirst.java.golden @@ -0,0 +1,206 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpaqueFirstJsonAdapter.class, nullSafe = false) +public sealed interface OpaqueFirst { + record VariantAny(JsonElement value0) implements OpaqueFirst {} + + record VariantFiniteTwo(FiniteTwo value0) implements OpaqueFirst {} +} + +final class OpaqueFirstJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpaqueFirst read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union OpaqueFirst: multiple concrete members match"); + } + selected = 1; + } + if (selected < 0 && true) { + selected = 0; + } + if (selected == 0) { + return new OpaqueFirst.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new OpaqueFirst.VariantFiniteTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown OpaqueFirst union value"); + } + + @Override + public void write(JsonWriter writer, OpaqueFirst value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof OpaqueFirst.VariantAny) { + gson.toJson(gson.toJsonTree(((OpaqueFirst.VariantAny) value).value0()), writer); + return; + } + if (value instanceof OpaqueFirst.VariantFiniteTwo) { + gson.toJson(gson.toJsonTree(((OpaqueFirst.VariantFiniteTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("flags") || !matches1(object.get("flags"))) { + return false; + } + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches8(object.get("second_value"))) { + return false; + } + if (!object.has("values") || !matches9(object.get("values"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + for (JsonElement item : candidate.getAsJsonObject().asMap().values()) { + if (!matches2(item)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + if (!matches3(candidate)) { + return false; + } + if (!matches4(candidate)) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches4(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches9(JsonElement candidate) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches10(item)) { + return false; + } + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + if (!matches11(candidate)) { + return false; + } + if (!matches12(candidate)) { + return false; + } + return true; + } + + private static boolean matches11(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches12(JsonElement candidate) { + if (matches7(candidate)) { + return true; + } + if (matches13(candidate)) { + return true; + } + return false; + } + + private static boolean matches13(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/OpenNested.java.golden b/tests/golden/java/java-okhttp/union-strict/models/OpenNested.java.golden new file mode 100644 index 000000000..44f34853d --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/OpenNested.java.golden @@ -0,0 +1,122 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = OpenNestedJsonAdapter.class, nullSafe = false) +public sealed interface OpenNested { + record VariantAmbiguousAlias(AmbiguousAlias value0) implements OpenNested {} + + record VariantAny(JsonElement value0) implements OpenNested {} +} + +final class OpenNestedJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public OpenNested read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union OpenNested: multiple concrete members match"); + } + selected = 0; + } + if (selected < 0 && true) { + selected = 1; + } + if (selected == 0) { + return new OpenNested.VariantAmbiguousAlias(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new OpenNested.VariantAny(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown OpenNested union value"); + } + + @Override + public void write(JsonWriter writer, OpenNested value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof OpenNested.VariantAmbiguousAlias) { + gson.toJson(gson.toJsonTree(((OpenNested.VariantAmbiguousAlias) value).value0()), writer); + return; + } + if (value instanceof OpenNested.VariantAny) { + gson.toJson(gson.toJsonTree(((OpenNested.VariantAny) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("value") || !matches1(object.get("value"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (matches2(candidate)) { + return true; + } + if (matches4(candidate)) { + return true; + } + return false; + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches3(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches3(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches4(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches3(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches3(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/RecursiveChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/RecursiveChoice.java.golden new file mode 100644 index 000000000..a36080efc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/RecursiveChoice.java.golden @@ -0,0 +1,70 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = RecursiveChoiceJsonAdapter.class, nullSafe = false) +public sealed interface RecursiveChoice { + record VariantRecursiveChoice(RecursiveChoice value0) implements RecursiveChoice {} + + record VariantString(String value0) implements RecursiveChoice {} +} + +final class RecursiveChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public RecursiveChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union RecursiveChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new RecursiveChoice.VariantRecursiveChoice(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new RecursiveChoice.VariantString(element.getAsString()); + } + throw new JsonParseException("unknown RecursiveChoice union value"); + } + + @Override + public void write(JsonWriter writer, RecursiveChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof RecursiveChoice.VariantRecursiveChoice) { + gson.toJson(gson.toJsonTree(((RecursiveChoice.VariantRecursiveChoice) value).value0()), writer); + return; + } + if (value instanceof RecursiveChoice.VariantString) { + gson.toJson(gson.toJsonTree(((RecursiveChoice.VariantString) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/RightPart.java.golden b/tests/golden/java/java-okhttp/union-strict/models/RightPart.java.golden new file mode 100644 index 000000000..4ab795f40 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/RightPart.java.golden @@ -0,0 +1,20 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class RightPart { + @SerializedName("right_value") + private int rightValue; + + public RightPart(int rightValue) { + this.rightValue = rightValue; + } + + public int getRightValue() { + return this.rightValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/ScalarChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/ScalarChoice.java.golden new file mode 100644 index 000000000..e9799d2a0 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/ScalarChoice.java.golden @@ -0,0 +1,205 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = ScalarChoiceJsonAdapter.class, nullSafe = false) +public sealed interface ScalarChoice { + record VariantInteger(int value0) implements ScalarChoice {} + + record VariantNumber(double value0) implements ScalarChoice {} + + record VariantBoolean(boolean value0) implements ScalarChoice {} +} + +final class ScalarChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public ScalarChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union ScalarChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches4(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union ScalarChoice: multiple concrete members match"); + } + selected = 1; + } + if (matches8(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union ScalarChoice: multiple concrete members match"); + } + selected = 2; + } + if (selected == 0) { + return new ScalarChoice.VariantInteger(gson.fromJson(element, Integer.class)); + } + if (selected == 1) { + return new ScalarChoice.VariantNumber(gson.fromJson(element, Double.class)); + } + if (selected == 2) { + return new ScalarChoice.VariantBoolean(element.getAsBoolean()); + } + throw new JsonParseException("unknown ScalarChoice union value"); + } + + @Override + public void write(JsonWriter writer, ScalarChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof ScalarChoice.VariantInteger) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantInteger) value).value0()), writer); + return; + } + if (value instanceof ScalarChoice.VariantNumber) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantNumber) value).value0()), writer); + return; + } + if (value instanceof ScalarChoice.VariantBoolean) { + gson.toJson(gson.toJsonTree(((ScalarChoice.VariantBoolean) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!matches1(candidate)) { + return false; + } + if (!matches2(candidate)) { + return false; + } + if (!matches3(candidate)) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static boolean matches3(JsonElement candidate) { + if (matches1(candidate)) { + return true; + } + if (matches2(candidate)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate) { + if (!matches5(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber(); + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2.5"); + } + + private static boolean matches7(JsonElement candidate) { + if (matches5(candidate)) { + return true; + } + if (matches6(candidate)) { + return true; + } + return false; + } + + private static boolean matches8(JsonElement candidate) { + if (!matches9(candidate)) { + return false; + } + if (!matches10(candidate)) { + return false; + } + if (!matches11(candidate)) { + return false; + } + return true; + } + + private static boolean matches9(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean(); + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isBoolean() && candidate.getAsBoolean() == true; + } + + private static boolean matches11(JsonElement candidate) { + if (matches9(candidate)) { + return true; + } + if (matches10(candidate)) { + return true; + } + return false; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Tree.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Tree.java.golden new file mode 100644 index 000000000..87a185614 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Tree.java.golden @@ -0,0 +1,210 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = TreeJsonAdapter.class, nullSafe = false) +public sealed interface Tree { + record VariantBranchOne(BranchOne value0) implements Tree {} + + record VariantBranchTwo(BranchTwo value0) implements Tree {} +} + +final class TreeJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public Tree read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + java.util.IdentityHashMap> memo = new java.util.IdentityHashMap>(); + if (matches0(element, memo)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Tree: multiple concrete members match"); + } + selected = 0; + } + if (matches4(element, memo)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union Tree: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new Tree.VariantBranchOne(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new Tree.VariantBranchTwo(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown Tree union value"); + } + + @Override + public void write(JsonWriter writer, Tree value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof Tree.VariantBranchOne) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranchOne) value).value0()), writer); + return; + } + if (value instanceof Tree.VariantBranchTwo) { + gson.toJson(gson.toJsonTree(((Tree.VariantBranchTwo) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + if (!object.has("revision") || !matches9(object.get("revision"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonArray()) { + return false; + } + for (JsonElement item : candidate.getAsJsonArray()) { + if (!matches2(item, memo)) { + return false; + } + } + return true; + } + + private static boolean matches2(JsonElement candidate, java.util.IdentityHashMap> memo) { + java.util.Map results = memo.get(candidate); + if (results == null) { + results = new java.util.HashMap<>(); + memo.put(candidate, results); + } + java.lang.Boolean cached = results.get(2); + if (cached != null) { + return cached; + } + boolean matched = matches3(candidate, memo); + results.put(2, matched); + return matched; + } + + private static boolean matches3(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (matches0(candidate, memo)) { + return true; + } + if (matches4(candidate, memo)) { + return true; + } + return false; + } + + private static boolean matches4(JsonElement candidate, java.util.IdentityHashMap> memo) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("children") || !matches1(object.get("children"), memo)) { + return false; + } + if (!object.has("revision") || !matches5(object.get("revision"))) { + return false; + } + if (object.has("second_value") && !matches8(object.get("second_value"))) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches7(candidate)) { + return false; + } + return true; + } + + private static boolean matches6(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches7(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches8(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches9(JsonElement candidate) { + if (!matches6(candidate)) { + return false; + } + if (!matches10(candidate)) { + return false; + } + return true; + } + + private static boolean matches10(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "1"); + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/VariantRaw.java.golden b/tests/golden/java/java-okhttp/union-strict/models/VariantRaw.java.golden new file mode 100644 index 000000000..4d73e541b --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/VariantRaw.java.golden @@ -0,0 +1,104 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = VariantRawJsonAdapter.class, nullSafe = false) +public sealed interface VariantRaw { + record VariantAlpha(Alpha value0) implements VariantRaw {} + + record VariantBeta(Beta value0) implements VariantRaw {} +} + +final class VariantRawJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public VariantRaw read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union VariantRaw: multiple concrete members match"); + } + selected = 0; + } + if (matches2(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union VariantRaw: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new VariantRaw.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new VariantRaw.VariantBeta(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown VariantRaw union value"); + } + + @Override + public void write(JsonWriter writer, VariantRaw value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof VariantRaw.VariantAlpha) { + gson.toJson(gson.toJsonTree(((VariantRaw.VariantAlpha) value).value0()), writer); + return; + } + if (value instanceof VariantRaw.VariantBeta) { + gson.toJson(gson.toJsonTree(((VariantRaw.VariantBeta) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches1(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches2(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("second_value") && !matches1(object.get("second_value"))) { + return false; + } + if (!object.has("wire_name") || !matches1(object.get("wire_name"))) { + return false; + } + return true; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/Wide.java.golden b/tests/golden/java/java-okhttp/union-strict/models/Wide.java.golden new file mode 100644 index 000000000..c64abc094 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/Wide.java.golden @@ -0,0 +1,26 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.annotations.SerializedName; + +public class Wide { + private long identifier; + @SerializedName("second_value") + private String secondValue; + + public Wide(long identifier, String secondValue) { + this.identifier = identifier; + this.secondValue = secondValue; + } + + public long getIdentifier() { + return this.identifier; + } + + public String getSecondValue() { + return this.secondValue; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/WrappedChoice.java.golden b/tests/golden/java/java-okhttp/union-strict/models/WrappedChoice.java.golden new file mode 100644 index 000000000..7c1d79d22 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/WrappedChoice.java.golden @@ -0,0 +1,169 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +@JsonAdapter(value = WrappedChoiceJsonAdapter.class, nullSafe = false) +public sealed interface WrappedChoice { + record VariantWrappedPair(WrappedPair value0) implements WrappedChoice {} + + record VariantAlpha(Alpha value0) implements WrappedChoice {} +} + +final class WrappedChoiceJsonAdapter extends TypeAdapter { + private final Gson gson = new Gson(); + + @Override + public WrappedChoice read(JsonReader reader) throws java.io.IOException { + JsonElement element = JsonParser.parseReader(reader); + if (element.isJsonNull()) { + return null; + } + int selected = -1; + if (matches0(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union WrappedChoice: multiple concrete members match"); + } + selected = 0; + } + if (matches7(element)) { + if (selected >= 0) { + throw new JsonParseException("ambiguous untagged union WrappedChoice: multiple concrete members match"); + } + selected = 1; + } + if (selected == 0) { + return new WrappedChoice.VariantWrappedPair(gson.fromJson(element, new TypeToken() {}.getType())); + } + if (selected == 1) { + return new WrappedChoice.VariantAlpha(gson.fromJson(element, new TypeToken() {}.getType())); + } + throw new JsonParseException("unknown WrappedChoice union value"); + } + + @Override + public void write(JsonWriter writer, WrappedChoice value) throws java.io.IOException { + if (value == null) { + writer.nullValue(); + return; + } + if (value instanceof WrappedChoice.VariantWrappedPair) { + gson.toJson(gson.toJsonTree(((WrappedChoice.VariantWrappedPair) value).value0()), writer); + return; + } + if (value instanceof WrappedChoice.VariantAlpha) { + gson.toJson(gson.toJsonTree(((WrappedChoice.VariantAlpha) value).value0()), writer); + return; + } + } + + private static boolean matches0(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("leftPart") || !matches1(object.get("leftPart"))) { + return false; + } + if (!object.has("rightPart") || !matches3(object.get("rightPart"))) { + return false; + } + return true; + } + + private static boolean matches1(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("left_value") || !matches2(object.get("left_value"))) { + return false; + } + return true; + } + + private static boolean matches2(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isString(); + } + + private static boolean matches3(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (!object.has("right_value") || !matches4(object.get("right_value"))) { + return false; + } + return true; + } + + private static boolean matches4(JsonElement candidate) { + if (!matches5(candidate)) { + return false; + } + if (!matches6(candidate)) { + return false; + } + return true; + } + + private static boolean matches5(JsonElement candidate) { + if (!candidate.isJsonPrimitive() || !candidate.getAsJsonPrimitive().isNumber()) { + return false; + } + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + return significand.signum() == 0 || numberExponent(literal).compareTo(java.math.BigInteger.valueOf(significand.scale())) >= 0; + } + + private static boolean matches6(JsonElement candidate) { + return candidate.isJsonPrimitive() && candidate.getAsJsonPrimitive().isNumber() && matchesNumber(candidate, "2"); + } + + private static boolean matches7(JsonElement candidate) { + if (!candidate.isJsonObject()) { + return false; + } + JsonObject object = candidate.getAsJsonObject(); + if (object.has("first_value") && !matches2(object.get("first_value"))) { + return false; + } + if (!object.has("wire_name") || !matches2(object.get("wire_name"))) { + return false; + } + return true; + } + + private static java.math.BigDecimal numberSignificand(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return new java.math.BigDecimal(exponent < 0 ? literal : literal.substring(0, exponent)).stripTrailingZeros(); + } + + private static java.math.BigInteger numberExponent(java.lang.String literal) { + int exponent = java.lang.Math.max(literal.indexOf('e'), literal.indexOf('E')); + return exponent < 0 ? java.math.BigInteger.ZERO : new java.math.BigInteger(literal.substring(exponent + 1)); + } + + private static boolean matchesNumber(JsonElement candidate, java.lang.String expected) { + java.lang.String literal = candidate.getAsString(); + java.math.BigDecimal significand = numberSignificand(literal); + java.math.BigDecimal expectedNumber = new java.math.BigDecimal(expected).stripTrailingZeros(); + if (!significand.unscaledValue().equals(expectedNumber.unscaledValue())) { + return false; + } + return significand.signum() == 0 || numberExponent(literal).equals(java.math.BigInteger.valueOf((long) significand.scale() - expectedNumber.scale())); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/models/WrappedPair.java.golden b/tests/golden/java/java-okhttp/union-strict/models/WrappedPair.java.golden new file mode 100644 index 000000000..18168e273 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/models/WrappedPair.java.golden @@ -0,0 +1,23 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.models; + +public class WrappedPair { + private LeftPart leftPart; + private RightPart rightPart; + + public WrappedPair(LeftPart leftPart, RightPart rightPart) { + this.leftPart = leftPart; + this.rightPart = rightPart; + } + + public LeftPart getLeftPart() { + return this.leftPart; + } + + public RightPart getRightPart() { + return this.rightPart; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/ApiClient.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/ApiClient.java.golden new file mode 100644 index 000000000..c45cce839 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/ApiClient.java.golden @@ -0,0 +1,78 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.io.IOException; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class ApiClient { + private static final MediaType JSON = MediaType.get("application/json"); + + private final String baseUrl; + private final OkHttpClient client; + private final Authenticator authenticator; + private final Map defaultHeaders; + + public ApiClient(String baseUrl) { + this(baseUrl, new OkHttpClient(), null, Map.of()); + } + + public ApiClient(String baseUrl, OkHttpClient client, Authenticator authenticator, Map defaultHeaders) { + this.baseUrl = baseUrl; + this.client = client; + this.authenticator = authenticator; + this.defaultHeaders = defaultHeaders; + } + + public Request newRequest(String method, String path, Map query, String body) { + RequestBody requestBody = null; + if (body != null) { + requestBody = RequestBody.create(body, JSON); + } + return buildRequest(method, path, query, requestBody); + } + + public Request newRequestWithBody(String method, String path, Map query, RequestBody body) { + return buildRequest(method, path, query, body); + } + + private Request buildRequest(String method, String path, Map query, RequestBody requestBody) { + String url = baseUrl.replaceAll("/+$", "") + path; + HttpUrl parsed = HttpUrl.parse(url); + if (parsed == null) { + throw new IllegalArgumentException("Invalid URL: " + url); + } + HttpUrl.Builder urlBuilder = parsed.newBuilder(); + if (query != null) { + for (Map.Entry entry : query.entrySet()) { + urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); + } + } + + Request.Builder builder = new Request.Builder() + .url(urlBuilder.build()) + .method(method, requestBody); + + for (Map.Entry entry : defaultHeaders.entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + if (authenticator != null) { + authenticator.authenticate(builder); + } + + return builder.build(); + } + + public Response execute(Request request) throws IOException { + return client.newCall(request).execute(); + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/ApiException.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/ApiException.java.golden new file mode 100644 index 000000000..b7ed957af --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/ApiException.java.golden @@ -0,0 +1,30 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public class ApiException extends RuntimeException { + private final int statusCode; + private final String status; + private final String body; + + public ApiException(int statusCode, String status, String body) { + super("API error " + statusCode + ": " + status); + this.statusCode = statusCode; + this.status = status; + this.body = body; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatus() { + return status; + } + + public String getBody() { + return body; + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyAuth.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyAuth.java.golden new file mode 100644 index 000000000..3b8992bdc --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyAuth.java.golden @@ -0,0 +1,41 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class ApiKeyAuth implements Authenticator { + private final String key; + private final Supplier keyProvider; + private final String name; + private final ApiKeyLocation location; + + public ApiKeyAuth(String key, String name, ApiKeyLocation location) { + this.key = key; + this.keyProvider = null; + this.name = name; + this.location = location; + } + + public ApiKeyAuth(Supplier keyProvider, String name, ApiKeyLocation location) { + this.key = null; + this.keyProvider = keyProvider; + this.name = name; + this.location = location; + } + + @Override + public void authenticate(Request.Builder builder) { + String k = keyProvider != null ? keyProvider.get() : key; + if (location == ApiKeyLocation.HEADER) { + builder.header(name, k); + } else if (location == ApiKeyLocation.QUERY) { + builder.url(builder.build().url().newBuilder() + .addQueryParameter(name, k) + .build()); + } + } +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyLocation.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyLocation.java.golden new file mode 100644 index 000000000..242937e90 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/ApiKeyLocation.java.golden @@ -0,0 +1,10 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +public enum ApiKeyLocation { + HEADER, + QUERY +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/Authenticator.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/Authenticator.java.golden new file mode 100644 index 000000000..f03b10463 --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/Authenticator.java.golden @@ -0,0 +1,11 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import okhttp3.Request; + +public interface Authenticator { + void authenticate(Request.Builder builder); +} diff --git a/tests/golden/java/java-okhttp/union-strict/runtime/BearerAuth.java.golden b/tests/golden/java/java-okhttp/union-strict/runtime/BearerAuth.java.golden new file mode 100644 index 000000000..4154f770c --- /dev/null +++ b/tests/golden/java/java-okhttp/union-strict/runtime/BearerAuth.java.golden @@ -0,0 +1,29 @@ +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Java union policies — 1.0.0 + +package com.example.sdk.runtime; + +import java.util.function.Supplier; +import okhttp3.Request; + +public class BearerAuth implements Authenticator { + private final String token; + private final Supplier tokenProvider; + + public BearerAuth(String token) { + this.token = token; + this.tokenProvider = null; + } + + public BearerAuth(Supplier tokenProvider) { + this.token = null; + this.tokenProvider = tokenProvider; + } + + @Override + public void authenticate(Request.Builder builder) { + String t = tokenProvider != null ? tokenProvider.get() : token; + builder.header("Authorization", "Bearer " + t); + } +} diff --git a/tests/golden_tests_java_okhttp.rs b/tests/golden_tests_java_okhttp.rs index 393eb4403..914c1f733 100644 --- a/tests/golden_tests_java_okhttp.rs +++ b/tests/golden_tests_java_okhttp.rs @@ -82,6 +82,7 @@ fn get_golden_test_cases() -> HashMap<&'static str, &'static str> { ("response-body-no-response-body", "valid/response-body/no-response-body.yaml"), ("typed-error-responses", "valid/typed-error-responses.yaml"), ("nested-nullable", "valid/nested-nullable.yaml"), + ("union-selection", "valid/union-selection.yaml"), ("additional-properties", "valid/additional-properties/additional-properties.yaml"), ("open-tagged-additional-properties", "valid/open-tagged-additional-properties.yaml"), @@ -113,6 +114,7 @@ macro_rules! generate_golden_tests { } generate_golden_tests! { + test_union_selection_golden: "union-selection", test_nested_nullable_golden: "nested-nullable", test_petstore_golden: "petstore", @@ -183,3 +185,38 @@ generate_golden_tests! { test_server_path_prefix_golden: "server-path-prefix", test_reserved_word_fields_golden: "reserved-word-fields", } + +#[test] +fn test_untagged_union_policies_golden() { + for (strategy, suffix) in [ + ("firstMatch", "first-match"), + ("strict", "strict"), + ("raw", "raw"), + ] { + let config = toml::from_str(&format!("untagged_union_strategy = '{strategy}'")).unwrap(); + run_golden_test( + &JavaOkhttpCodeGenerator::new(config), + golden_dir(), + &format!("union-{suffix}"), + "valid/java-union-policies.yaml", + UPDATE_HINT, + ); + } +} + +#[test] +fn test_recursive_union_helper_types_golden() { + for (strategy, case) in [ + ("firstMatch", "union-helper-types"), + ("strict", "union-helper-types-strict"), + ] { + let config = toml::from_str(&format!("untagged_union_strategy = '{strategy}'")).unwrap(); + run_golden_test( + &JavaOkhttpCodeGenerator::new(config), + golden_dir(), + case, + "valid/java-union-helper-types.yaml", + UPDATE_HINT, + ); + } +} diff --git a/tests/ir_scalar_enums.rs b/tests/ir_scalar_enums.rs index 73dcbdeaf..d1a073cf8 100644 --- a/tests/ir_scalar_enums.rs +++ b/tests/ir_scalar_enums.rs @@ -167,7 +167,7 @@ components: } #[test] -fn scalar_enum_metadata_changes_only_rust_union_dispatch_and_not_public_representations() { +fn scalar_enum_metadata_changes_union_dispatch_without_changing_public_representations() { let source = r#"openapi: 3.1.0 info: {title: Scalar representations, version: '1'} paths: @@ -310,6 +310,20 @@ components: assert!(actual.contains("Some(true)")); continue; } + if generator.generator_type().language() == openapi_nexus::codegen::Language::Java + && filename == "Models/Choice.java" + { + let actual = &actual[filename]; + assert_ne!(actual, expected, "Java dispatch consumes finite facts"); + assert_eq!( + actual.split("\nfinal class ChoiceJsonAdapter").next(), + expected.split("\nfinal class ChoiceJsonAdapter").next(), + "Java typed cases and payloads do not change" + ); + assert!(actual.contains("matchesNumber(candidate, \"1\")")); + assert!(actual.contains("candidate.getAsBoolean() == true")); + continue; + } assert_eq!( actual.get(filename), Some(expected),