From 07cd3e967cf86e1e72335eafac57aa18d695f6c0 Mon Sep 17 00:00:00 2001 From: Adam Basfop Cavendish Date: Wed, 11 Nov 2026 12:19:17 +0800 Subject: [PATCH] fix: preserve Rust untagged-union member payloads - Match retained scalar enum and structural facts before choosing a Rust union member, preventing unconstrained integer fields from swallowing later payloads. - Share firstMatch, strict, and explicit raw policies across reqwest, ureq, and aioduct while retaining typed variants and untagged serialization by default. - Count authored concrete candidates before opaque fallbacks and deserialize the selected member once, retaining nested errors and recursive boundaries. - Memoize recursive shape/node results during selection to avoid exponential subtree scans without changing candidate order or caching across decoded values. - Keep exact large-integer comparisons, nullable finite constraints, native byte arrays, and concrete entries in open recursive dictionaries during selection. - Emit union declarations and serde/utoipa methods structurally; normalize owned derive paths, including absolute names, without duplicate trait implementations. - Propagate tagged model emission errors with their original model/source context instead of silently dropping failed output. - Retain native model and HTTP consumer regressions, exact renders, and all three Rust golden suites without changing other languages or broader model codecs. --- CHANGELOG.md | 6 + docs/src/rust_config.md | 78 ++- scripts/generate-rust-client-fixtures.sh | 17 +- src/generators/python/value.rs | 1 + src/generators/rust/common/config.rs | 53 +- src/generators/rust/common/emit_models.rs | 203 ++++--- src/generators/rust/common/matching.rs | 515 ++++++++++++++++++ src/generators/rust/common/mod.rs | 2 + src/generators/rust/common/union.rs | 279 ++++++++++ src/generators/union_match.rs | 8 +- tests/fixtures/valid/rust-union-policies.yaml | 194 +++++++ tests/fixtures/valid/union-selection.yaml | 66 +++ .../generated-clients/rust/aioduct/Cargo.lock | 78 +++ .../generated-clients/rust/aioduct/Cargo.toml | 6 + .../rust/aioduct/tests/union_http.rs | 17 + .../rust/aioduct/tests/union_policies.rs | 2 + .../rust/aioduct/tests/union_selection.rs | 2 + .../generated-clients/rust/reqwest/Cargo.lock | 78 +++ .../generated-clients/rust/reqwest/Cargo.toml | 6 + .../rust/reqwest/tests/union_http.rs | 14 + .../rust/reqwest/tests/union_policies.rs | 2 + .../rust/reqwest/tests/union_selection.rs | 2 + .../rust/support/union_http.rs | 54 ++ .../rust/support/union_policies.rs | 212 +++++++ .../rust/support/union_selection.rs | 80 +++ .../rust/union-policies.toml | 31 ++ tests/generated-clients/rust/ureq/Cargo.lock | 107 +++- tests/generated-clients/rust/ureq/Cargo.toml | 6 + .../rust/ureq/tests/union_http.rs | 14 + .../rust/ureq/tests/union_policies.rs | 2 + .../rust/ureq/tests/union_selection.rs | 2 + .../src/models/any_of_types.rs.golden | 30 +- .../src/models/one_of_objects.rs.golden | 90 ++- .../src/models/one_of_types.rs.golden | 38 +- .../src/models/one_of_with_null.rs.golden | 51 +- .../enum-repr/src/models/mixed_enum.rs.golden | 104 +++- .../src/models/untagged_enum.rs.golden | 66 ++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 80 ++- .../src/models/qux.rs.golden | 62 ++- .../src/models/foo.rs.golden | 52 +- .../src/models/config.rs.golden | 34 +- .../src/models/foo.rs.golden | 99 +++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 38 +- .../union-first-match/Cargo.toml.golden | 13 + .../union-first-match/README.md.golden | 7 + .../union-first-match/src/apis/mod.rs.golden | 7 + .../src/apis/selection.rs.golden | 104 ++++ .../union-first-match/src/lib.rs.golden | 10 + .../src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 78 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 54 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 126 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 256 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 133 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 94 ++++ .../src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 82 +++ .../src/models/map_two.rs.golden | 12 + .../src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 256 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 147 +++++ .../src/models/opaque_first.rs.golden | 64 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 88 +++ .../src/models/open_tree.rs.golden | 82 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 54 ++ .../src/models/recursive_map_outer.rs.golden | 101 ++++ .../src/models/scalar_choice.rs.golden | 161 ++++++ .../src/models/tree.rs.golden | 151 +++++ .../src/runtime/auth.rs.golden | 181 ++++++ .../src/runtime/client.rs.golden | 111 ++++ .../src/runtime/error.rs.golden | 229 ++++++++ .../src/runtime/mod.rs.golden | 8 + .../rust-aioduct/union-raw/Cargo.toml.golden | 13 + .../rust-aioduct/union-raw/README.md.golden | 7 + .../union-raw/src/apis/mod.rs.golden | 7 + .../union-raw/src/apis/selection.rs.golden | 104 ++++ .../rust-aioduct/union-raw/src/lib.rs.golden | 10 + .../union-raw/src/models/alpha.rs.golden | 13 + .../union-raw/src/models/ambiguous.rs.golden | 27 + .../src/models/ambiguous_alias.rs.golden | 10 + .../union-raw/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 27 + .../union-raw/src/models/branch_one.rs.golden | 12 + .../union-raw/src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 27 + .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 27 + .../union-raw/src/models/finite_one.rs.golden | 15 + .../union-raw/src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 27 + .../union-raw/src/models/large_one.rs.golden | 11 + .../union-raw/src/models/large_two.rs.golden | 13 + .../union-raw/src/models/map_choice.rs.golden | 27 + .../union-raw/src/models/map_one.rs.golden | 12 + .../union-raw/src/models/map_outer.rs.golden | 27 + .../union-raw/src/models/map_two.rs.golden | 12 + .../union-raw/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 27 + .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 27 + .../src/models/opaque_first.rs.golden | 27 + .../src/models/open_branch.rs.golden | 12 + .../union-raw/src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 27 + .../union-raw/src/models/open_tree.rs.golden | 27 + .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 27 + .../src/models/recursive_map_outer.rs.golden | 27 + .../src/models/scalar_choice.rs.golden | 27 + .../union-raw/src/models/tree.rs.golden | 27 + .../union-raw/src/runtime/auth.rs.golden | 181 ++++++ .../union-raw/src/runtime/client.rs.golden | 111 ++++ .../union-raw/src/runtime/error.rs.golden | 229 ++++++++ .../union-raw/src/runtime/mod.rs.golden | 8 + .../union-selection/Cargo.toml.golden | 12 + .../union-selection/README.md.golden | 7 + .../union-selection/src/apis/mod.rs.golden | 5 + .../union-selection/src/lib.rs.golden | 10 + .../src/models/choice.rs.golden | 103 ++++ .../src/models/client_tool.rs.golden | 12 + .../src/models/envelope.rs.golden | 13 + .../union-selection/src/models/mod.rs.golden | 27 + .../src/models/numeric_one.rs.golden | 13 + .../src/models/numeric_two.rs.golden | 13 + .../src/models/request.rs.golden | 11 + .../src/models/required_choice.rs.golden | 103 ++++ .../src/models/required_one.rs.golden | 12 + .../src/models/required_two.rs.golden | 12 + .../src/models/search_tool.rs.golden | 15 + .../union-selection/src/models/tool.rs.golden | 82 +++ .../src/runtime/auth.rs.golden | 181 ++++++ .../src/runtime/client.rs.golden | 111 ++++ .../src/runtime/error.rs.golden | 106 ++++ .../union-selection/src/runtime/mod.rs.golden | 8 + .../union-strict/Cargo.toml.golden | 13 + .../union-strict/README.md.golden | 7 + .../union-strict/src/apis/mod.rs.golden | 7 + .../union-strict/src/apis/selection.rs.golden | 104 ++++ .../union-strict/src/lib.rs.golden | 10 + .../union-strict/src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 84 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../union-strict/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 60 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 132 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 262 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 139 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 100 ++++ .../union-strict/src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 85 +++ .../union-strict/src/models/map_two.rs.golden | 12 + .../union-strict/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 262 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 153 ++++++ .../src/models/opaque_first.rs.golden | 67 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 91 ++++ .../src/models/open_tree.rs.golden | 85 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 60 ++ .../src/models/recursive_map_outer.rs.golden | 104 ++++ .../src/models/scalar_choice.rs.golden | 170 ++++++ .../union-strict/src/models/tree.rs.golden | 157 ++++++ .../union-strict/src/runtime/auth.rs.golden | 181 ++++++ .../union-strict/src/runtime/client.rs.golden | 111 ++++ .../union-strict/src/runtime/error.rs.golden | 229 ++++++++ .../union-strict/src/runtime/mod.rs.golden | 8 + .../src/models/feed_source.rs.golden | 10 +- .../src/models/payment_method.rs.golden | 67 ++- .../src/models/event.rs.golden | 97 +++- .../src/models/any_of_types.rs.golden | 30 +- .../src/models/one_of_objects.rs.golden | 90 ++- .../src/models/one_of_types.rs.golden | 38 +- .../src/models/one_of_with_null.rs.golden | 51 +- .../enum-repr/src/models/mixed_enum.rs.golden | 104 +++- .../src/models/untagged_enum.rs.golden | 66 ++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 80 ++- .../src/models/qux.rs.golden | 62 ++- .../src/models/foo.rs.golden | 52 +- .../src/models/config.rs.golden | 34 +- .../src/models/foo.rs.golden | 99 +++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 38 +- .../union-first-match/Cargo.toml.golden | 13 + .../union-first-match/README.md.golden | 7 + .../union-first-match/src/apis/mod.rs.golden | 7 + .../src/apis/selection.rs.golden | 104 ++++ .../union-first-match/src/lib.rs.golden | 10 + .../src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 78 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 54 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 126 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 256 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 133 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 94 ++++ .../src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 82 +++ .../src/models/map_two.rs.golden | 12 + .../src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 256 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 147 +++++ .../src/models/opaque_first.rs.golden | 64 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 88 +++ .../src/models/open_tree.rs.golden | 82 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 54 ++ .../src/models/recursive_map_outer.rs.golden | 101 ++++ .../src/models/scalar_choice.rs.golden | 161 ++++++ .../src/models/tree.rs.golden | 151 +++++ .../src/runtime/auth.rs.golden | 188 +++++++ .../src/runtime/client.rs.golden | 72 +++ .../src/runtime/error.rs.golden | 228 ++++++++ .../src/runtime/mod.rs.golden | 8 + .../rust-reqwest/union-raw/Cargo.toml.golden | 13 + .../rust-reqwest/union-raw/README.md.golden | 7 + .../union-raw/src/apis/mod.rs.golden | 7 + .../union-raw/src/apis/selection.rs.golden | 104 ++++ .../rust-reqwest/union-raw/src/lib.rs.golden | 10 + .../union-raw/src/models/alpha.rs.golden | 13 + .../union-raw/src/models/ambiguous.rs.golden | 27 + .../src/models/ambiguous_alias.rs.golden | 10 + .../union-raw/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 27 + .../union-raw/src/models/branch_one.rs.golden | 12 + .../union-raw/src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 27 + .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 27 + .../union-raw/src/models/finite_one.rs.golden | 15 + .../union-raw/src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 27 + .../union-raw/src/models/large_one.rs.golden | 11 + .../union-raw/src/models/large_two.rs.golden | 13 + .../union-raw/src/models/map_choice.rs.golden | 27 + .../union-raw/src/models/map_one.rs.golden | 12 + .../union-raw/src/models/map_outer.rs.golden | 27 + .../union-raw/src/models/map_two.rs.golden | 12 + .../union-raw/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 27 + .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 27 + .../src/models/opaque_first.rs.golden | 27 + .../src/models/open_branch.rs.golden | 12 + .../union-raw/src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 27 + .../union-raw/src/models/open_tree.rs.golden | 27 + .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 27 + .../src/models/recursive_map_outer.rs.golden | 27 + .../src/models/scalar_choice.rs.golden | 27 + .../union-raw/src/models/tree.rs.golden | 27 + .../union-raw/src/runtime/auth.rs.golden | 188 +++++++ .../union-raw/src/runtime/client.rs.golden | 72 +++ .../union-raw/src/runtime/error.rs.golden | 228 ++++++++ .../union-raw/src/runtime/mod.rs.golden | 8 + .../union-selection/Cargo.toml.golden | 12 + .../union-selection/README.md.golden | 7 + .../union-selection/src/apis/mod.rs.golden | 5 + .../union-selection/src/lib.rs.golden | 10 + .../src/models/choice.rs.golden | 103 ++++ .../src/models/client_tool.rs.golden | 12 + .../src/models/envelope.rs.golden | 13 + .../union-selection/src/models/mod.rs.golden | 27 + .../src/models/numeric_one.rs.golden | 13 + .../src/models/numeric_two.rs.golden | 13 + .../src/models/request.rs.golden | 11 + .../src/models/required_choice.rs.golden | 103 ++++ .../src/models/required_one.rs.golden | 12 + .../src/models/required_two.rs.golden | 12 + .../src/models/search_tool.rs.golden | 15 + .../union-selection/src/models/tool.rs.golden | 82 +++ .../src/runtime/auth.rs.golden | 188 +++++++ .../src/runtime/client.rs.golden | 72 +++ .../src/runtime/error.rs.golden | 105 ++++ .../union-selection/src/runtime/mod.rs.golden | 8 + .../union-strict/Cargo.toml.golden | 13 + .../union-strict/README.md.golden | 7 + .../union-strict/src/apis/mod.rs.golden | 7 + .../union-strict/src/apis/selection.rs.golden | 104 ++++ .../union-strict/src/lib.rs.golden | 10 + .../union-strict/src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 84 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../union-strict/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 60 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 132 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 262 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 139 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 100 ++++ .../union-strict/src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 85 +++ .../union-strict/src/models/map_two.rs.golden | 12 + .../union-strict/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 262 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 153 ++++++ .../src/models/opaque_first.rs.golden | 67 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 91 ++++ .../src/models/open_tree.rs.golden | 85 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 60 ++ .../src/models/recursive_map_outer.rs.golden | 104 ++++ .../src/models/scalar_choice.rs.golden | 170 ++++++ .../union-strict/src/models/tree.rs.golden | 157 ++++++ .../union-strict/src/runtime/auth.rs.golden | 188 +++++++ .../union-strict/src/runtime/client.rs.golden | 72 +++ .../union-strict/src/runtime/error.rs.golden | 228 ++++++++ .../union-strict/src/runtime/mod.rs.golden | 8 + .../src/models/feed_source.rs.golden | 10 +- .../src/models/payment_method.rs.golden | 67 ++- .../src/models/event.rs.golden | 97 +++- .../src/models/any_of_types.rs.golden | 30 +- .../src/models/one_of_objects.rs.golden | 90 ++- .../src/models/one_of_types.rs.golden | 38 +- .../src/models/one_of_with_null.rs.golden | 51 +- .../enum-repr/src/models/mixed_enum.rs.golden | 104 +++- .../src/models/untagged_enum.rs.golden | 66 ++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 80 ++- .../src/models/qux.rs.golden | 62 ++- .../src/models/foo.rs.golden | 52 +- .../src/models/config.rs.golden | 34 +- .../src/models/foo.rs.golden | 99 +++- .../src/models/foo.rs.golden | 90 ++- .../src/models/foo.rs.golden | 38 +- .../union-first-match/Cargo.toml.golden | 13 + .../union-first-match/README.md.golden | 7 + .../union-first-match/src/apis/mod.rs.golden | 7 + .../src/apis/selection.rs.golden | 104 ++++ .../union-first-match/src/lib.rs.golden | 10 + .../src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 78 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 54 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 126 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 256 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 133 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 94 ++++ .../src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 82 +++ .../src/models/map_two.rs.golden | 12 + .../src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 256 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 147 +++++ .../src/models/opaque_first.rs.golden | 64 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 88 +++ .../src/models/open_tree.rs.golden | 82 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 54 ++ .../src/models/recursive_map_outer.rs.golden | 101 ++++ .../src/models/scalar_choice.rs.golden | 161 ++++++ .../src/models/tree.rs.golden | 151 +++++ .../src/runtime/auth.rs.golden | 168 ++++++ .../src/runtime/client.rs.golden | 93 ++++ .../src/runtime/error.rs.golden | 227 ++++++++ .../src/runtime/mod.rs.golden | 8 + .../rust-ureq/union-raw/Cargo.toml.golden | 13 + .../rust/rust-ureq/union-raw/README.md.golden | 7 + .../union-raw/src/apis/mod.rs.golden | 7 + .../union-raw/src/apis/selection.rs.golden | 104 ++++ .../rust-ureq/union-raw/src/lib.rs.golden | 10 + .../union-raw/src/models/alpha.rs.golden | 13 + .../union-raw/src/models/ambiguous.rs.golden | 27 + .../src/models/ambiguous_alias.rs.golden | 10 + .../union-raw/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 27 + .../union-raw/src/models/branch_one.rs.golden | 12 + .../union-raw/src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 27 + .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 27 + .../union-raw/src/models/finite_one.rs.golden | 15 + .../union-raw/src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 27 + .../union-raw/src/models/large_one.rs.golden | 11 + .../union-raw/src/models/large_two.rs.golden | 13 + .../union-raw/src/models/map_choice.rs.golden | 27 + .../union-raw/src/models/map_one.rs.golden | 12 + .../union-raw/src/models/map_outer.rs.golden | 27 + .../union-raw/src/models/map_two.rs.golden | 12 + .../union-raw/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 27 + .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 27 + .../src/models/opaque_first.rs.golden | 27 + .../src/models/open_branch.rs.golden | 12 + .../union-raw/src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 27 + .../union-raw/src/models/open_tree.rs.golden | 27 + .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 27 + .../src/models/recursive_map_outer.rs.golden | 27 + .../src/models/scalar_choice.rs.golden | 27 + .../union-raw/src/models/tree.rs.golden | 27 + .../union-raw/src/runtime/auth.rs.golden | 168 ++++++ .../union-raw/src/runtime/client.rs.golden | 93 ++++ .../union-raw/src/runtime/error.rs.golden | 227 ++++++++ .../union-raw/src/runtime/mod.rs.golden | 8 + .../union-selection/Cargo.toml.golden | 12 + .../union-selection/README.md.golden | 7 + .../union-selection/src/apis/mod.rs.golden | 5 + .../union-selection/src/lib.rs.golden | 10 + .../src/models/choice.rs.golden | 103 ++++ .../src/models/client_tool.rs.golden | 12 + .../src/models/envelope.rs.golden | 13 + .../union-selection/src/models/mod.rs.golden | 27 + .../src/models/numeric_one.rs.golden | 13 + .../src/models/numeric_two.rs.golden | 13 + .../src/models/request.rs.golden | 11 + .../src/models/required_choice.rs.golden | 103 ++++ .../src/models/required_one.rs.golden | 12 + .../src/models/required_two.rs.golden | 12 + .../src/models/search_tool.rs.golden | 15 + .../union-selection/src/models/tool.rs.golden | 82 +++ .../src/runtime/auth.rs.golden | 168 ++++++ .../src/runtime/client.rs.golden | 93 ++++ .../src/runtime/error.rs.golden | 104 ++++ .../union-selection/src/runtime/mod.rs.golden | 8 + .../rust-ureq/union-strict/Cargo.toml.golden | 13 + .../rust-ureq/union-strict/README.md.golden | 7 + .../union-strict/src/apis/mod.rs.golden | 7 + .../union-strict/src/apis/selection.rs.golden | 104 ++++ .../rust-ureq/union-strict/src/lib.rs.golden | 10 + .../union-strict/src/models/alpha.rs.golden | 13 + .../src/models/ambiguous.rs.golden | 84 +++ .../src/models/ambiguous_alias.rs.golden | 10 + .../union-strict/src/models/beta.rs.golden | 13 + .../src/models/binary_choice.rs.golden | 60 ++ .../src/models/branch_one.rs.golden | 12 + .../src/models/branch_two.rs.golden | 14 + .../src/models/container_choice.rs.golden | 132 +++++ .../src/models/container_one.rs.golden | 13 + .../src/models/container_two.rs.golden | 13 + .../src/models/excluded_null.rs.golden | 12 + .../src/models/finite_choice.rs.golden | 262 +++++++++ .../src/models/finite_one.rs.golden | 15 + .../src/models/finite_two.rs.golden | 15 + .../src/models/included_null.rs.golden | 14 + .../src/models/large_choice.rs.golden | 139 +++++ .../src/models/large_one.rs.golden | 11 + .../src/models/large_two.rs.golden | 13 + .../src/models/map_choice.rs.golden | 100 ++++ .../union-strict/src/models/map_one.rs.golden | 12 + .../src/models/map_outer.rs.golden | 85 +++ .../union-strict/src/models/map_two.rs.golden | 12 + .../union-strict/src/models/mod.rs.golden | 77 +++ .../src/models/nullable_choice.rs.golden | 262 +++++++++ .../src/models/nullable_envelope.rs.golden | 12 + .../src/models/nullable_fields.rs.golden | 153 ++++++ .../src/models/opaque_first.rs.golden | 67 +++ .../src/models/open_branch.rs.golden | 12 + .../src/models/open_map.rs.golden | 12 + .../src/models/open_nested.rs.golden | 91 ++++ .../src/models/open_tree.rs.golden | 85 +++ .../src/models/open_tree_map.rs.golden | 12 + .../src/models/optional_opaque.rs.golden | 12 + .../models/optional_opaque_choice.rs.golden | 60 ++ .../src/models/recursive_map_outer.rs.golden | 104 ++++ .../src/models/scalar_choice.rs.golden | 170 ++++++ .../union-strict/src/models/tree.rs.golden | 157 ++++++ .../union-strict/src/runtime/auth.rs.golden | 168 ++++++ .../union-strict/src/runtime/client.rs.golden | 93 ++++ .../union-strict/src/runtime/error.rs.golden | 227 ++++++++ .../union-strict/src/runtime/mod.rs.golden | 8 + .../src/models/feed_source.rs.golden | 10 +- .../src/models/payment_method.rs.golden | 67 ++- .../src/models/event.rs.golden | 97 +++- tests/golden_tests_rust_aioduct.rs | 33 ++ tests/golden_tests_rust_reqwest.rs | 33 ++ tests/golden_tests_rust_ureq.rs | 33 ++ tests/ir_scalar_enums.rs | 16 +- tests/rust_error_generation.rs | 64 ++- 561 files changed, 29736 insertions(+), 279 deletions(-) create mode 100644 src/generators/rust/common/matching.rs create mode 100644 src/generators/rust/common/union.rs create mode 100644 tests/fixtures/valid/rust-union-policies.yaml create mode 100644 tests/fixtures/valid/union-selection.yaml create mode 100644 tests/generated-clients/rust/aioduct/tests/union_http.rs create mode 100644 tests/generated-clients/rust/aioduct/tests/union_policies.rs create mode 100644 tests/generated-clients/rust/aioduct/tests/union_selection.rs create mode 100644 tests/generated-clients/rust/reqwest/tests/union_http.rs create mode 100644 tests/generated-clients/rust/reqwest/tests/union_policies.rs create mode 100644 tests/generated-clients/rust/reqwest/tests/union_selection.rs create mode 100644 tests/generated-clients/rust/support/union_http.rs create mode 100644 tests/generated-clients/rust/support/union_policies.rs create mode 100644 tests/generated-clients/rust/support/union_selection.rs create mode 100644 tests/generated-clients/rust/union-policies.toml create mode 100644 tests/generated-clients/rust/ureq/tests/union_http.rs create mode 100644 tests/generated-clients/rust/ureq/tests/union_policies.rs create mode 100644 tests/generated-clients/rust/ureq/tests/union_selection.rs create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-first-match/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-raw/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/client_tool.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/envelope.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/request.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/required_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/required_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/required_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/search_tool.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/models/tool.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/README.md.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-aioduct/union-strict/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-first-match/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-raw/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/client_tool.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/envelope.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/request.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/required_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/required_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/required_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/search_tool.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/models/tool.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/README.md.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-reqwest/union-strict/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-first-match/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-raw/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/client_tool.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/envelope.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/numeric_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/numeric_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/request.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/required_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/required_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/required_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/search_tool.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/models/tool.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-selection/src/runtime/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/Cargo.toml.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/README.md.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/apis/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/apis/selection.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/lib.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/alpha.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous_alias.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/beta.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/binary_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/branch_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/branch_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/container_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/container_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/container_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/excluded_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/finite_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/finite_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/finite_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/included_null.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/large_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/large_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/large_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/map_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/map_one.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/map_two.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/mod.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/nullable_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/nullable_envelope.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/nullable_fields.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/opaque_first.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/open_branch.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/open_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/open_nested.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/open_tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/open_tree_map.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/recursive_map_outer.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/scalar_choice.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/models/tree.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/runtime/auth.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/runtime/client.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/runtime/error.rs.golden create mode 100644 tests/golden/rust/rust-ureq/union-strict/src/runtime/mod.rs.golden diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b35b51b..c60ca9915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- 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 + containers and recursive members. Add `firstMatch`, `strict`, and explicit + `Raw(serde_json::Value)` policies; propagate selected-member decoding errors + without hiding them behind another member or an opaque fallback. - Convert Python httpx/requests untagged-union members in models, nested containers, aliases, JSON/multipart requests, responses, and lazy error bodies. Share enum- aware selection across both transports with `firstMatch`, `strict`, and dynamic diff --git a/docs/src/rust_config.md b/docs/src/rust_config.md index acb1940bb..9a7b77896 100644 --- a/docs/src/rust_config.md +++ b/docs/src/rust_config.md @@ -82,30 +82,80 @@ struct AppError { } ``` +## Untagged Union Deserialization + +Untagged unions keep their existing typed variants and untagged serialization. +Deserialization selects members using the shared IR's JSON shapes, required wire +keys, finite scalar values (including numeric and boolean enums), nested arrays +and maps, and productive recursive references. This prevents an earlier member +with an unconstrained Rust integer field from swallowing a later member's payload. +Repeated recursive checks reuse results within one member-selection pass, without +changing candidate order or sharing cached results across decoded values. + +### `untagged_union_strategy` + +| Value | Generated API and behavior | +| --- | --- | +| `"firstMatch"` (default) | Keep typed variants; select the first structural match in schema order, preferring concrete members to opaque fallbacks. | +| `"strict"` | Keep the same typed API; reject multiple concrete matches before deserializing any member. Use an opaque fallback only when no concrete member matches. | +| `"raw"` | Explicitly replace each untagged union with an untagged enum containing only `Raw(serde_json::Value)`. Preserve known, ambiguous, unmatched, and null JSON without typed selection. | + +Both typed policies reject unmatched values. After selection, the chosen member +is deserialized once; its errors (including nested strict-union errors) propagate +instead of being hidden by another member or an opaque fallback. Structural +matching is not a full JSON Schema validator: formats, bounds, and other validation +keywords are not selection rules, and native Serde decoding can still reject a +selected payload. Serialization does not add schema validation. + +The typed enum does not gain synthetic `Raw` or `Null` variants. Explicitly nullable +use-site expressions continue to use `Option`; decode a standalone nullable +union through `Option` as well. As before, a named union's root nullable +flag alone does not change its Rust enum or automatically wrap references to it. +Explicit `raw` changes the union's Rust API, so callers +construct and match `Union::Raw(value)` rather than typed variants. It does not +change tagged unions or turn ordinary models into raw JSON. + +```toml +[rust-reqwest] +untagged_union_strategy = "strict" +``` + +The same setting works for `rust-ureq` and `rust-aioduct`, or as a CLI override: + +```sh +openapi-nexus generate --input api.yaml --output sdk --generators rust-reqwest \ + --generator-config rust-reqwest.untagged_union_strategy=raw +``` + +Typed decoding retains the selected member's declared payload, not arbitrary +unknown fields. Dictionary/additional-property representation and multi-member +`allOf` behavior are unchanged. Choose `raw` when the full JSON must be retained. + ## Full Example ```toml -[generators.rust-reqwest] +[rust-reqwest] crate_name = "my-api-client" workspace_mode = true workspace_deps = "workspace_version" +untagged_union_strategy = "firstMatch" -[generators.rust-reqwest.extra_derives.structs] +[rust-reqwest.extra_derives.structs] derives = ["PartialEq", "Eq"] -[generators.rust-reqwest.extra_derives.enums] +[rust-reqwest.extra_derives.enums] derives = ["Hash"] -[generators.rust-reqwest.extra_derives.unions] +[rust-reqwest.extra_derives.unions] derives = ["PartialEq"] -[generators.rust-reqwest.extra_derives.response_structs] +[rust-reqwest.extra_derives.response_structs] derives = ["PartialEq"] -[generators.rust-reqwest.extra_derives.per_type.MySpecialSchema] +[rust-reqwest.extra_derives.per_type.MySpecialSchema] derives = ["Default"] -[generators.rust-reqwest.utoipa] +[rust-reqwest.utoipa] enabled = true dependency = '{ version = "5" }' ``` @@ -148,23 +198,27 @@ Add custom derive macros to generated types. Each category targets a different s - `structs` — object schemas - `enums` — string and integer enums -- `unions` — tagged unions (external tagging) +- `unions` — tagged and untagged unions (including explicit raw wrappers) - `response_structs` — per-operation response type wrappers ```toml -[generators.rust-reqwest.extra_derives.structs] +[rust-reqwest.extra_derives.structs] derives = ["PartialEq", "Eq"] dependencies = { fake = '"2"' } ``` The `dependencies` field adds entries to the generated `Cargo.toml`. +For untagged unions, the emitter owns Serde implementations and, when enabled, +utoipa implementations. Equivalent short, crate-qualified, and absolute derive +paths (such as `Deserialize`, `serde::Deserialize`, and `::serde::Deserialize`) +do not add conflicting implementations. ### `extra_derives.per_type` Target a specific schema by name: ```toml -[generators.rust-reqwest.extra_derives.per_type.UserProfile] +[rust-reqwest.extra_derives.per_type.UserProfile] derives = ["Default", "Hash"] ``` @@ -173,7 +227,7 @@ derives = ["Default", "Hash"] Native [utoipa](https://github.com/juhaku/utoipa) integration for OpenAPI schema generation at runtime. ```toml -[generators.rust-reqwest.utoipa] +[rust-reqwest.utoipa] enabled = true dependency = '{ version = "5" }' ``` @@ -194,7 +248,7 @@ You do NOT need to add `utoipa::ToSchema` to `extra_derives` when using this con Configure aioduct-specific dependency features for the generated crate. This section only applies to the `rust-aioduct` generator. ```toml -[generators.rust-aioduct.aioduct] +[rust-aioduct.aioduct] version = "0.2" runtime = "tokio" tls = "rustls-ring" diff --git a/scripts/generate-rust-client-fixtures.sh b/scripts/generate-rust-client-fixtures.sh index 12a690cfd..cf3f47b5c 100644 --- a/scripts/generate-rust-client-fixtures.sh +++ b/scripts/generate-rust-client-fixtures.sh @@ -10,7 +10,7 @@ esac root="$(cd "$(dirname "$0")/.." && pwd)" project="$root/tests/generated-clients/rust/$backend" -for fixture in typed-error-responses rust-error-payload-shapes nested-nullable; do +for fixture in typed-error-responses rust-error-payload-shapes nested-nullable union-selection; do output="$project/generated/$fixture" # These fixed fixture directories contain only disposable generated output. # Remove stale source files so a deleted declaration cannot pass unnoticed. @@ -21,3 +21,18 @@ for fixture in typed-error-responses rust-error-payload-shapes nested-nullable; --output "$output" \ --generators "rust-$backend" done + +for strategy in firstMatch strict raw; do + suffix="$strategy" + if [ "$strategy" = firstMatch ]; then suffix=first-match; fi + output="$project/generated/union-$suffix" + # Fixed disposable output, never a consumer source directory. + rm -rf "$output" + cargo run --quiet --manifest-path "$root/Cargo.toml" --bin openapi-nexus -- generate \ + --config "$root/tests/generated-clients/rust/union-policies.toml" \ + --input "$root/tests/fixtures/valid/rust-union-policies.yaml" \ + --output "$output" \ + --generators "rust-$backend" \ + --generator-config "rust-$backend.crate_name=rust-union-$suffix" \ + --generator-config "rust-$backend.untagged_union_strategy=$strategy" +done diff --git a/src/generators/python/value.rs b/src/generators/python/value.rs index c48d38c16..e8840f1bd 100644 --- a/src/generators/python/value.rs +++ b/src/generators/python/value.rs @@ -582,6 +582,7 @@ impl<'a> ValuePlan<'a> { type_expr, shape, fallback, + .. } in candidates { let check = condition( diff --git a/src/generators/rust/common/config.rs b/src/generators/rust/common/config.rs index b7be1fd67..f8a143f42 100644 --- a/src/generators/rust/common/config.rs +++ b/src/generators/rust/common/config.rs @@ -80,8 +80,9 @@ impl ExtraDerivesConfig { fn check_derives(derives: &[String], deps: &BTreeMap) { for d in derives { - if let Some(crate_name) = d.split("::").next() - && crate_name != d + let path = d.strip_prefix("::").unwrap_or(d); + if let Some(crate_name) = path.split("::").next() + && crate_name != path { let normalized = crate_name.replace('-', "_"); if !deps.contains_key(&normalized) { @@ -119,6 +120,21 @@ pub struct UtoipaConfig { pub dependency: Option, } +/// Deserialization policy for untagged unions; serialization stays untagged. +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] +pub enum UntaggedUnionStrategy { + /// Select the first structural match, preferring concrete members to fallbacks. + #[default] + #[serde(rename = "firstMatch")] + FirstMatch, + /// Reject multiple concrete matches before decoding the selected member. + #[serde(rename = "strict")] + Strict, + /// Emit a raw JSON wrapper instead of selecting typed members. + #[serde(rename = "raw")] + Raw, +} + /// Base configuration for Rust generators. #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct RustGeneratorConfig { @@ -133,6 +149,8 @@ pub struct RustGeneratorConfig { #[serde(default)] pub utoipa: Option, #[serde(default)] + pub untagged_union_strategy: UntaggedUnionStrategy, + #[serde(default)] pub aioduct: Option, } @@ -167,6 +185,26 @@ mod tests { assert!(config.extra_derives.is_none()); } + #[test] + fn untagged_union_strategy_is_explicit_and_defaults_to_first_match() { + assert_eq!( + RustGeneratorConfig::default().untagged_union_strategy, + UntaggedUnionStrategy::FirstMatch + ); + for (value, expected) in [ + ("firstMatch", UntaggedUnionStrategy::FirstMatch), + ("strict", UntaggedUnionStrategy::Strict), + ("raw", UntaggedUnionStrategy::Raw), + ] { + let config: RustGeneratorConfig = + 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() + ); + } + #[test] fn deserialize_empty_table() { let config = RustGeneratorConfig::from(toml::value::Table::new()); @@ -394,4 +432,15 @@ mod tests { }; extra.warn_missing_dependencies(); } + + #[test] + #[tracing_test::traced_test] + fn absolute_derive_paths_check_the_named_crate_dependency() { + let deps = BTreeMap::from([("custom".into(), "\"1\"".into())]); + ExtraDerivesConfig::check_derives(&["::custom::Derive".into()], &deps); + assert!(!logs_contain("no matching entry")); + ExtraDerivesConfig::check_derives(&["::missing::Derive".into()], &deps); + assert!(logs_contain("references crate `missing`")); + assert!(!logs_contain("references crate ``")); + } } diff --git a/src/generators/rust/common/emit_models.rs b/src/generators/rust/common/emit_models.rs index e6823b57e..bb1b17ef7 100644 --- a/src/generators/rust/common/emit_models.rs +++ b/src/generators/rust/common/emit_models.rs @@ -6,7 +6,7 @@ //! - `Object` — struct with serde derives and `#[serde(rename)]` tags. //! - `Enum` — Rust enum with serde rename per variant. //! - `Alias` — `pub type X = Y;` -//! - `Union` — `#[serde(untagged)]` enum. +//! - `Union` — typed untagged enum with structural member selection, or raw JSON. //! - `Intersection` — struct with `#[serde(flatten)]` fields. //! - `TaggedUnion` — serde-tagged enum (internal/adjacent/external). @@ -19,12 +19,16 @@ use crate::generators::request_inputs::{ use crate::generators::{ArtifactKind, GeneratorError}; use crate::ir::types::{ IrEnum, IrEnumValueType, IrIntersection, IrObject, IrPrimitive, IrSchema, IrSchemaKind, IrSpec, - IrTaggedUnion, IrTypeExpr, IrUnion, TaggingStyle, + IrTaggedUnion, IrTypeExpr, TaggingStyle, }; use heck::{ToPascalCase, ToSnakeCase}; +use sigil_stitch::error::SigilStitchError; +use sigil_stitch::lang::rust::Rust; use sigil_stitch::prelude::{CodeBlock, sigil_quote}; use sigil_stitch::spec::file_spec::FileSpec; +use sigil_stitch::spec::fun_spec::FunSpec; use sigil_stitch::spec::import_spec::ImportSpec; +use sigil_stitch::spec::modifiers::DeclarationContext; use sigil_stitch::spec::where_spec::TypeArgument; use sigil_stitch::type_name::TypeName; @@ -91,7 +95,10 @@ pub fn generate_model_files( if inlined_schemas.contains(schema.name.as_str()) { continue; } - let Some(file_spec) = emit_model_file(schema, config, ir) else { + let Some(file_spec) = emit_model_file(schema, config, ir).map_err(|source| { + GeneratorError::emission(ArtifactKind::Model, &schema.name, source) + })? + else { continue; }; let stem = schema.name.to_snake_case(); @@ -160,13 +167,13 @@ fn emit_model_file( schema: &IrSchema, config: &RustGeneratorConfig, ir: &IrSpec, -) -> Option { +) -> Result, SigilStitchError> { let extra = config.extra_derives.as_ref(); let utoipa_enabled = config.utoipa.as_ref().is_some_and(|u| u.enabled); let per_type_cfg = extra .and_then(|e| e.per_type.as_ref()) .and_then(|m| m.get(&schema.name)); - match &schema.kind { + Ok(match &schema.kind { IrSchemaKind::Object(obj) => { let derives = merge_utoipa( per_type_cfg.or_else(|| extra.and_then(|e| e.structs.as_ref())), @@ -188,7 +195,16 @@ fn emit_model_file( ); emit_alias(schema, expr, derives.as_ref()) } - IrSchemaKind::Union(u) => emit_union(schema, u, per_type_cfg, utoipa_enabled), + IrSchemaKind::Union(u) => { + return super::union::emit_union( + schema, + u, + per_type_cfg.or_else(|| extra.and_then(|e| e.unions.as_ref())), + config, + ir, + ) + .map(Some); + } IrSchemaKind::Intersection(i) => { let derives = merge_utoipa( per_type_cfg.or_else(|| extra.and_then(|e| e.structs.as_ref())), @@ -200,9 +216,9 @@ fn emit_model_file( let derives = per_type_cfg .or_else(|| extra.and_then(|e| e.unions.as_ref())) .cloned(); - emit_tagged_union(schema, tu, derives.as_ref(), ir, utoipa_enabled) + return emit_tagged_union(schema, tu, derives.as_ref(), ir, utoipa_enabled); } - } + }) } // --------------------------------------------------------------------------- @@ -474,62 +490,10 @@ fn emit_type_alias_file(schema: &IrSchema, rhs_str: &str) -> Option { } // --------------------------------------------------------------------------- -// Union -> #[serde(untagged)] enum +// Union variant naming (the typed public API is independent of dispatch policy) // --------------------------------------------------------------------------- -fn emit_union( - schema: &IrSchema, - union: &IrUnion, - extra: Option<&ExtraDeriveConfig>, - utoipa_enabled: bool, -) -> Option { - let name = schema.name.to_pascal_case(); - let stem = schema.name.to_snake_case(); - - let mut fsb = FileSpec::builder(&format!("{stem}.rs")); - fsb = fsb.add_import(ImportSpec::named("serde", "Deserialize")); - fsb = fsb.add_import(ImportSpec::named("serde", "Serialize")); - - let variants: Vec<(String, String)> = union - .members - .iter() - .enumerate() - .map(|(i, member)| { - let variant_name = union_variant_name(member, i); - let rust_type = rust_type_str_model(member); - (variant_name, rust_type) - }) - .collect(); - - let body = sigil_quote!(RustLang { - $if(schema.description.is_some()) { - $L(doc_comment_block(schema.description.as_deref().unwrap()).trim_end()) - } - $attr(derive_attr("Debug, Clone, Serialize, Deserialize", extra)) - #[serde(untagged)] - pub enum $N(name.as_str()) { - $for((variant_name, rust_type) in variants.iter()) { - $N(variant_name.as_str())($L(rust_type.as_str())), - } - } - }) - .ok()?; - - fsb = fsb.add_code(body); - - if utoipa_enabled { - let variant_types: Vec = variants - .iter() - .map(|(_, rust_type)| rust_type.clone()) - .collect(); - let impl_block = build_utoipa_one_of_impl(&name, &variant_types)?; - fsb = fsb.add_code(impl_block); - } - - fsb.build().ok() -} - -fn union_variant_name(expr: &IrTypeExpr, index: usize) -> String { +pub(super) fn union_variant_name(expr: &IrTypeExpr, index: usize) -> String { match expr.representation() { IrTypeExpr::Named(n) => n.to_pascal_case(), IrTypeExpr::Primitive(p) => primitive_variant_name(p), @@ -562,9 +526,9 @@ fn emit_tagged_union( extra: Option<&ExtraDeriveConfig>, ir: &IrSpec, utoipa_enabled: bool, -) -> Option { +) -> Result, SigilStitchError> { if tu.variants.is_empty() { - return None; + return Ok(None); } let name = schema.name.to_pascal_case(); @@ -621,8 +585,7 @@ fn emit_tagged_union( $attr(rename_attr.as_str()) } $N(variant_name.as_str()), - }) - .ok()? + })? } else { let field_blocks: Vec = non_tag_fields .iter() @@ -645,9 +608,8 @@ fn emit_tagged_union( } $N(field_name.as_str()): $T(field_type), }) - .ok() }) - .collect::>()?; + .collect::>()?; let additional_blocks: Vec = if let Some(ap) = &obj.additional_properties { @@ -659,13 +621,10 @@ fn emit_tagged_union( TypeArgument::Single(TypeName::raw(&ty)), ], ); - vec![ - sigil_quote!(RustLang { - #[serde(flatten)] - additional_properties: $T(additional_type), - }) - .ok()?, - ] + vec![sigil_quote!(RustLang { + #[serde(flatten)] + additional_properties: $T(additional_type), + })?] } else { vec![] }; @@ -678,14 +637,13 @@ fn emit_tagged_union( $C_each(field_blocks) $C_each(additional_blocks) } - }) - .ok()?; + })?; // The renderer's structured block emits a trailing newline; // trim it so the static enum-item comma stays adjacent. let mut variant_block = CodeBlock::builder(); variant_block.add_code(variant_body.__sigil_trim_trailing_newline()); variant_block.add(",", ()); - variant_block.build().ok()? + variant_block.build()? } } else { let content_ty = rust_type_str_model(&variant.content_type); @@ -694,8 +652,7 @@ fn emit_tagged_union( $attr(rename_attr.as_str()) } $N(variant_name.as_str())($L(content_ty.as_str())), - }) - .ok()? + })? } } else { let content_ty = rust_type_str_model(&variant.content_type); @@ -704,8 +661,7 @@ fn emit_tagged_union( $attr(rename_attr.as_str()) } $N(variant_name.as_str())($L(content_ty.as_str())), - }) - .ok()? + })? }; variant_blocks.push(variant_block); @@ -722,22 +678,21 @@ fn emit_tagged_union( pub enum $N(name.as_str()) { $C_each(variant_blocks); } - }) - .ok()?; + })?; fsb = fsb.add_code(body); if utoipa_enabled { - let variant_types: Vec = tu + let variant_types: Vec = tu .variants .iter() - .map(|v| rust_type_str_model(&v.content_type)) + .map(|v| rust_model_type(&v.content_type)) .collect(); let impl_block = build_utoipa_one_of_impl(&name, &variant_types)?; fsb = fsb.add_code(impl_block); } - fsb.build().ok() + fsb.build().map(Some) } // --------------------------------------------------------------------------- @@ -793,30 +748,45 @@ fn emit_intersection( // Utoipa impl helpers // --------------------------------------------------------------------------- -fn build_utoipa_one_of_impl(name: &str, variant_types: &[String]) -> Option { - let items: Vec = variant_types - .iter() - .map(|rt| format!(" .item(<{rt} as utoipa::PartialSchema>::schema())")) - .collect(); - +pub(super) fn build_utoipa_one_of_impl( + name: &str, + variant_types: &[TypeName], +) -> Result { + let schema = FunSpec::builder("schema") + .returns(vec![crate::generators::type_application( + TypeName::qualified("utoipa::openapi", "RefOr"), + vec![TypeName::qualified("utoipa::openapi::schema", "Schema")], + )]) + .body(sigil_quote!(RustLang { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + $for(variant in variant_types) { + .item(<$T(variant.clone()) as utoipa::PartialSchema>::schema()) + } + .build() + ).into() + })?) + .build()? + .emit(&Rust::new(), DeclarationContext::Member)?; + let schema_name = FunSpec::builder("name") + // TypeArgument cannot represent lifetimes in sigil-stitch 0.7.5. + .returns(vec![crate::generators::type_application( + TypeName::qualified("std::borrow", "Cow"), + vec![TypeName::raw("'static"), TypeName::primitive("str")], + )]) + .body(sigil_quote!(RustLang { std::borrow::Cow::Borrowed($S(name)) })?) + .build()? + .emit(&Rust::new(), DeclarationContext::Member)?; + // No trait-implementation spec exists; keep the methods structured and + // quote only the surrounding implementation blocks. sigil_quote!(RustLang { impl utoipa::PartialSchema for $N(name) { - fn schema() -> utoipa::openapi::RefOr { - $L("utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new()") - $for(item in items.iter()) { - $L(item.as_str()) - } - $L(" .build()).into()") - } + $C(schema) } - impl utoipa::ToSchema for $N(name) { - fn name() -> std::borrow::Cow<'static, str> { - std::borrow::Cow::Borrowed($S(name)) - } + $C(schema_name) } }) - .ok() } // --------------------------------------------------------------------------- @@ -924,6 +894,29 @@ pub fn rust_type_str_qualified(expr: &IrTypeExpr, ir: &IrSpec) -> String { } /// Map an IR type for use within model files (sibling references use `super::`). +pub(super) fn rust_model_type(expr: &IrTypeExpr) -> TypeName { + use crate::generators::type_application; + + match expr.representation() { + IrTypeExpr::Named(name) => TypeName::qualified("super", &name.to_pascal_case()), + IrTypeExpr::Array(inner) => { + type_application(TypeName::primitive("Vec"), vec![rust_model_type(inner)]) + } + IrTypeExpr::Map(inner) => type_application( + TypeName::qualified("std::collections", "HashMap"), + vec![TypeName::primitive("String"), rust_model_type(inner)], + ), + IrTypeExpr::Nullable(inner) => TypeName::optional(rust_model_type(inner)), + IrTypeExpr::Primitive(IrPrimitive::Binary) => { + type_application(TypeName::primitive("Vec"), vec![TypeName::primitive("u8")]) + } + IrTypeExpr::Primitive(primitive) => TypeName::primitive(rust_primitive(primitive)), + IrTypeExpr::StringLiteral(_) | IrTypeExpr::StringEnum(_) => TypeName::primitive("String"), + IrTypeExpr::Union(_) | IrTypeExpr::Any => TypeName::qualified("serde_json", "Value"), + IrTypeExpr::ScalarEnum { .. } => unreachable!("representation unwraps scalar enums"), + } +} + fn rust_type_str_model(expr: &IrTypeExpr) -> String { match expr.representation() { IrTypeExpr::Named(name) => format!("super::{}", name.to_pascal_case()), diff --git a/src/generators/rust/common/matching.rs b/src/generators/rust/common/matching.rs new file mode 100644 index 000000000..43f86e000 --- /dev/null +++ b/src/generators/rust/common/matching.rs @@ -0,0 +1,515 @@ +//! Rust predicates for the shared structural union-selection plan. +//! +//! Private, deduplicated functions keep recursive graphs finite without shipping +//! an interpreter or weakening recursive edges to an unrestricted JSON match. +//! Recursive references memoize results during one immutable JSON selection so +//! overlapping members do not repeatedly walk the same subtree. + +use sigil_stitch::error::SigilStitchError; +use sigil_stitch::prelude::{CodeBlock, TypeName, sigil_quote}; +use sigil_stitch::spec::{fun_spec::FunSpec, parameter_spec::ParameterSpec}; + +use crate::generators::union_match::{ + JsonShape, MatchScope, accepts_any, plan_named_shape, selection_shapes, +}; +use crate::ir::types::IrSpec; + +#[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::AnyOf(_) | JsonShape::AllOf(_) | JsonShape::Nullable(_) + if accepts_any(shape) => + { + Some(true) + } + // Retained finite facts are scalar; composite enum values are not + // evidence for choosing a typed converter. + 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::Array(inner) | JsonShape::Map(inner) | JsonShape::Nullable(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("union matcher shape was registered") + } + + fn symbol(&self, shape: &JsonShape) -> String { + format!("__nexus_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::Array(inner) | JsonShape::Map(inner) | JsonShape::Nullable(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 condition( + &self, + shape: &JsonShape, + candidate: CodeBlock, + ) -> Result { + match Self::constant(shape) { + Some(true) => sigil_quote!(RustLang { true }), + Some(false) => sigil_quote!(RustLang { false }), + None => { + let symbol = self.symbol(shape); + let recursive = Self::recursive(shape); + sigil_quote!(RustLang { + $N(symbol)($C(candidate)$if(recursive) { , memo }) + }) + } + } + } + + pub(super) fn functions(&self, ir: &IrSpec) -> Result, SigilStitchError> { + self.shapes + .iter() + .map(|shape| { + let mut function = + FunSpec::builder(&self.symbol(shape)).add_param(ParameterSpec::of( + "candidate", + TypeName::reference(TypeName::qualified("serde_json", "Value")), + )); + if Self::recursive(shape) { + let memo = crate::generators::type_application( + TypeName::qualified("std::collections", "HashMap"), + vec![ + TypeName::tuple(vec![ + TypeName::primitive("usize"), + TypeName::pointer(TypeName::qualified("serde_json", "Value")), + ]), + TypeName::primitive("bool"), + ], + ); + function = function + .add_param(ParameterSpec::of("memo", TypeName::reference_mut(memo))); + } + function + .returns(vec![TypeName::primitive("bool")]) + .body(self.function_body(shape, ir)?) + .build() + }) + .collect() + } + + fn function_body(&self, shape: &JsonShape, ir: &IrSpec) -> Result { + let body = self.body(shape, ir)?; + if !matches!(shape, JsonShape::Reference { .. }) { + return Ok(body); + } + let index = self.index(shape).to_string(); + // The JSON tree is immutable and alive for this selection. Pointer + // identity is only a local cache key: it is never dereferenced or shared + // across selections. The shape index keeps reference scopes distinct. + // A closure retains early returns while caching both true and false. + sigil_quote!(RustLang { + let key = ($L(index), std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { return *result; } + let result = (|| { $C(body) })(); + memo.insert(key, result); + result + }) + } + + fn body(&self, shape: &JsonShape, ir: &IrSpec) -> Result { + match shape { + JsonShape::Any => sigil_quote!(RustLang { true }), + JsonShape::Never => sigil_quote!(RustLang { false }), + JsonShape::Null => sigil_quote!(RustLang { candidate.is_null() }), + JsonShape::Boolean => sigil_quote!(RustLang { candidate.is_boolean() }), + JsonShape::String => sigil_quote!(RustLang { candidate.is_string() }), + JsonShape::Number { integer: true } => { + sigil_quote!(RustLang { candidate.is_i64() || candidate.is_u64() }) + } + JsonShape::Number { integer: false } => { + sigil_quote!(RustLang { candidate.is_number() }) + } + JsonShape::Binary => sigil_quote!(RustLang { + candidate.as_array().is_some_and(|items| { + items.iter().all(|item| item.as_u64().is_some_and(|number| number <= 255)) + }) + }), + JsonShape::Nullable(inner) => { + let inner = self.condition(inner, sigil_quote!(RustLang { candidate })?)?; + sigil_quote!(RustLang { candidate.is_null() || $C(inner) }) + } + JsonShape::Literal(value) => literal_body(value), + JsonShape::Array(inner) => { + if Self::constant(inner) == Some(true) { + return sigil_quote!(RustLang { candidate.is_array() }); + } + if Self::constant(inner) == Some(false) { + return sigil_quote!(RustLang { + candidate.as_array().is_some_and(|items| items.is_empty()) + }); + } + let check = self.condition(inner, sigil_quote!(RustLang { item })?)?; + sigil_quote!(RustLang { + candidate.as_array().is_some_and(|items| items.iter().all(|item| $C(check))) + }) + } + JsonShape::Map(inner) => { + if Self::constant(inner) == Some(true) { + return sigil_quote!(RustLang { candidate.is_object() }); + } + if Self::constant(inner) == Some(false) { + return sigil_quote!(RustLang { + candidate.as_object().is_some_and(|items| items.is_empty()) + }); + } + let check = self.condition(inner, sigil_quote!(RustLang { item })?)?; + sigil_quote!(RustLang { + candidate.as_object().is_some_and(|items| items.values().all(|item| $C(check))) + }) + } + JsonShape::MapWithConcreteValue { values, concrete } => { + let values = self.condition( + &JsonShape::Map(values.clone()), + sigil_quote!(RustLang { candidate })?, + )?; + let concrete = self.condition(concrete, sigil_quote!(RustLang { item })?)?; + sigil_quote!(RustLang { + if !$C(values) { return false; } + candidate.as_object().is_some_and(|items| items.values().any(|item| $C(concrete))) + }) + } + JsonShape::Object(properties) => { + if properties.is_empty() { + return sigil_quote!(RustLang { candidate.is_object() }); + } + let checks = properties + .iter() + .filter(|property| { + property.required || Self::constant(&property.shape) != Some(true) + }) + .map(|property| { + let key = &property.wire_name; + if Self::constant(&property.shape) == Some(true) { + return sigil_quote!(RustLang { + if !object.contains_key($S(key)) { return false; } + }); + } + let item = if Self::constant(&property.shape).is_some() { + "_item" + } else { + "item" + }; + let check = + self.condition(&property.shape, sigil_quote!(RustLang { $N(item) })?)?; + if property.required { + sigil_quote!(RustLang { + if !object.get($S(key)).is_some_and(|$N(item)| $C(check)) { + return false; + } + }) + } else { + sigil_quote!(RustLang { + if object.get($S(key)).is_some_and(|$N(item)| !$C(check)) { + return false; + } + }) + } + }) + .collect::, SigilStitchError>>()?; + if checks.is_empty() { + return sigil_quote!(RustLang { candidate.is_object() }); + } + sigil_quote!(RustLang { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + $C_each(checks) + true + }) + } + JsonShape::ObjectWithAdditional { properties, values } => { + let object = self.condition( + &JsonShape::Object(properties.clone()), + sigil_quote!(RustLang { candidate })?, + )?; + if Self::constant(values) == Some(true) { + return sigil_quote!(RustLang { $C(object) }); + } + let item = if Self::constant(values).is_some() { + "_item" + } else { + "item" + }; + let check = self.condition(values, sigil_quote!(RustLang { $N(item) })?)?; + let keys: Vec<_> = properties + .iter() + .map(|property| &property.wire_name) + .collect(); + sigil_quote!(RustLang { + if !$C(object) { return false; } + let declared = [$for(key in &keys; separator = ", ") { $S(key) }]; + candidate.as_object().is_some_and(|object| { + object.iter().all(|(key, $N(item))| declared.contains(&key.as_str()) || $C(check)) + }) + }) + } + 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!(RustLang { candidate })?)?; + if disjunction { + sigil_quote!(RustLang { if $C(check) { return true; } }) + } else { + sigil_quote!(RustLang { if !$C(check) { return false; } }) + } + }) + .collect::, _>>()?; + sigil_quote!(RustLang { + $C_each(checks) + $if(disjunction) { false } $else { true } + }) + } + JsonShape::Reference { schema, scope, .. } => { + let shape = plan_named_shape(schema, ir); + if *scope == MatchScope::Any { + return self.condition(&shape, sigil_quote!(RustLang { candidate })?); + } + let (concrete, fallback) = selection_shapes(shape); + let concrete = self.condition(&concrete, sigil_quote!(RustLang { candidate })?)?; + if *scope == MatchScope::Concrete { + return Ok(concrete); + } + let fallback = self.condition(&fallback, sigil_quote!(RustLang { candidate })?)?; + sigil_quote!(RustLang { + if $C(concrete) { return false; } + $C(fallback) + }) + } + } + } +} + +fn literal_body(value: &serde_json::Value) -> Result { + match value { + serde_json::Value::Null => sigil_quote!(RustLang { candidate.is_null() }), + serde_json::Value::Bool(value) => { + sigil_quote!(RustLang { candidate.as_bool() == Some($L(value.to_string())) }) + } + serde_json::Value::String(value) => { + sigil_quote!(RustLang { candidate.as_str() == Some($S(value)) }) + } + serde_json::Value::Number(number) => number_body(number), + _ => sigil_quote!(RustLang { false }), + } +} + +fn number_body(number: &serde_json::Number) -> Result { + // Compare integral JSON numbers exactly, including values above 2^53. Only + // cross the float/integer boundary when a finite float fits that integer; + // converting both integers to f64 would conflate adjacent enum values. + if number.is_i64() || number.is_u64() { + let signed = number.is_i64(); + let integer = format!("{number}_{}", if signed { "i64" } else { "u64" }); + let accessor = if signed { "as_i64" } else { "as_u64" }; + let integer_type = TypeName::primitive(if signed { "i64" } else { "u64" }); + return sigil_quote!(RustLang { + if !candidate.is_f64() { + return candidate.$N(accessor)() == Some($L(integer.clone())); + } + candidate.as_f64().is_some_and(|number| { + number.fract() == 0.0 + && $if(signed) { number >= i64::MIN as f64 && number < -(i64::MIN as f64) } + $else { number >= 0.0 && number < u64::MAX as f64 } + && number as $T(integer_type) == $L(integer) + }) + }); + } + let number = format!("{number}_f64"); + sigil_quote!(RustLang { + let expected = $L(number); + if candidate.is_f64() { return candidate.as_f64() == Some(expected); } + if expected.fract() != 0.0 { return false; } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::generators::union_match::PropertyMatch; + use sigil_stitch::assert_rendered; + use sigil_stitch::lang::rust::Rust; + use sigil_stitch::spec::modifiers::DeclarationContext; + + #[test] + fn recursive_reference_predicates_cache_results_by_shape_and_json_node() { + let ir = crate::ir::lower::lower( + crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/rust-union-policies.yaml" + )) + .unwrap(), + ) + .unwrap(); + 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!( + Rust::new(), + width = 100, + plan.functions(&ir).unwrap()[0] + .emit(&Rust::new(), DeclarationContext::TopLevel) + .unwrap(), + r#"fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (0, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_1(candidate, memo)})(); + memo.insert(key, result); + result +} +"#, + ); + } + + #[test] + fn object_matchers_keep_match_statement_terminators_and_wire_key_escaping() { + let ir = crate::ir::lower::lower( + crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/union-selection.yaml" + )) + .unwrap(), + ) + .unwrap(); + let shape = JsonShape::Object(vec![PropertyMatch { + wire_name: "wire\"key".into(), + rename: false, + required: true, + shape: JsonShape::String, + }]); + let mut plan = MatchPlan::default(); + plan.register(&shape, &ir); + let body = plan.body(&shape, &ir).unwrap(); + assert_rendered!( + Rust::new(), + width = 100, + body, + r#"let object = match candidate.as_object() { + Some(object) => object, + None => return false, +}; +if!object.get("wire\"key").is_some_and(|item | __nexus_matches_1(item)) { + return false; +} +true +"#, + ); + } + + #[test] + fn scalar_guard_literals_preserve_boolean_and_numeric_json_kinds() { + assert_rendered!( + Rust::new(), + literal_body(&true.into()).unwrap(), + "candidate.as_bool() == Some(true)\n" + ); + assert_rendered!( + Rust::new(), + literal_body(&"wire\"value".into()).unwrap(), + "candidate.as_str() == Some(\"wire\\\"value\")\n" + ); + } +} diff --git a/src/generators/rust/common/mod.rs b/src/generators/rust/common/mod.rs index 4665fc76b..e53ca0844 100644 --- a/src/generators/rust/common/mod.rs +++ b/src/generators/rust/common/mod.rs @@ -1,5 +1,7 @@ pub mod config; pub mod emit_api; pub mod emit_models; +mod matching; pub mod project_files; pub(crate) mod runtime; +mod union; diff --git a/src/generators/rust/common/union.rs b/src/generators/rust/common/union.rs new file mode 100644 index 000000000..21896a6c0 --- /dev/null +++ b/src/generators/rust/common/union.rs @@ -0,0 +1,279 @@ +//! Typed untagged unions with enum-aware, policy-controlled deserialization. + +use heck::{ToPascalCase, ToSnakeCase}; +use sigil_stitch::error::SigilStitchError; +use sigil_stitch::lang::rust::Rust; +use sigil_stitch::prelude::{TypeName, sigil_quote}; +use sigil_stitch::spec::{ + annotation_spec::AnnotationSpec, + closed_sum_case_spec::ClosedSumCaseSpec, + closed_sum_spec::ClosedSumSpec, + file_spec::FileSpec, + fun_spec::FunSpec, + import_spec::ImportSpec, + modifiers::{DeclarationContext, Visibility}, + parameter_spec::ParameterSpec, + where_spec::GenericParamSpec, +}; + +use crate::generators::type_application; +use crate::generators::union_match::plan_union_members; +use crate::ir::types::{IrSchema, IrSpec, IrUnion}; + +use super::config::{ExtraDeriveConfig, RustGeneratorConfig, UntaggedUnionStrategy}; +use super::emit_models::{build_utoipa_one_of_impl, rust_model_type, union_variant_name}; +use super::matching::MatchPlan; + +pub(super) fn emit_union( + schema: &IrSchema, + union: &IrUnion, + extra: Option<&ExtraDeriveConfig>, + config: &RustGeneratorConfig, + ir: &IrSpec, +) -> Result { + let name = schema.name.to_pascal_case(); + let raw = config.untagged_union_strategy == UntaggedUnionStrategy::Raw; + let utoipa = config.utoipa.as_ref().is_some_and(|utoipa| utoipa.enabled); + let variants: Vec<_> = if raw { + vec![( + "Raw".to_string(), + TypeName::qualified("serde_json", "Value"), + )] + } else { + union + .members + .iter() + .enumerate() + .map(|(index, member)| (union_variant_name(member, index), rust_model_type(member))) + .collect() + }; + + let mut sum = ClosedSumSpec::builder(&name) + .visibility(Visibility::Public) + .annotate(derives(extra, raw, utoipa)) + .annotate(AnnotationSpec::new("serde").arg("untagged")); + if let Some(description) = &schema.description { + for line in description.lines() { + sum = sum.doc(line); + } + } + for (variant, payload) in &variants { + sum = sum.add_case(ClosedSumCaseSpec::positional( + variant, + vec![payload.clone()], + )?); + } + let mut file = FileSpec::builder(&format!("{}.rs", schema.name.to_snake_case())) + .add_import(ImportSpec::named("serde", "Serialize")) + .add_closed_sum(sum.build()?); + + if raw { + file = file.add_import(ImportSpec::named("serde", "Deserialize")); + } + + if !raw { + let members = plan_union_members(&schema.name, union, ir); + let mut matching = MatchPlan::default(); + for member in &members { + matching.register(&member.shape, ir); + } + let strict = config.untagged_union_strategy == UntaggedUnionStrategy::Strict; + let ambiguous = format!("ambiguous untagged union {name}: multiple concrete members match"); + let unmatched = format!("value does not match any member of untagged union {name}"); + let mut selection = Vec::new(); + for member in &members { + let condition = + matching.condition(&member.shape, sigil_quote!(RustLang { &candidate })?)?; + let index = member.source_index; + selection.push(if strict && !member.fallback { + sigil_quote!(RustLang { + if $C(condition) { + if selected.is_some() { return Err(serde::de::Error::custom($S(&ambiguous))); } + selected = Some($L(index.to_string())); + } + })? + } else { + sigil_quote!(RustLang { + if selected.is_none() && $C(condition) { + selected = Some($L(index.to_string())); + } + })? + }); + } + let body = sigil_quote!(RustLang { + let candidate = ::deserialize(deserializer)?; + $if(matching.is_recursive()) { + let memo = &mut std::collections::HashMap::new(); + } + let mut selected = None; + $C_each(selection) + match selected { + $for((index, (variant, payload)) in variants.iter().enumerate()) { + Some($L(index.to_string())) => serde_json::from_value::<$T(payload.clone())>(candidate) + .map(Self::$N(variant)) + .map_err(serde::de::Error::custom), + } + _ => Err(serde::de::Error::custom($S(unmatched))), + } + })?; + // TypeArgument has no lifetime variant in sigil-stitch 0.7.5. Keep + // only that atomic lifetime raw; the trait application stays semantic. + let deserializer = type_application( + TypeName::qualified("serde", "Deserializer"), + vec![TypeName::raw("'de")], + ); + let method = FunSpec::builder("deserialize") + .add_generic_param(GenericParamSpec::single("D")?.with_bound(deserializer)?) + .add_param(ParameterSpec::of("deserializer", TypeName::primitive("D"))) + .returns(vec![type_application( + TypeName::primitive("Result"), + vec![ + TypeName::primitive("Self"), + TypeName::qualified("D", "Error"), + ], + )]) + .body(body) + .build()? + .emit(&Rust::new(), DeclarationContext::Member)?; + // sigil-stitch has no trait-implementation spec. The method itself is + // a FunSpec; the impl boundary is a structured quoted fragment. + file = file.add_code(sigil_quote!(RustLang { + impl<'de> serde::Deserialize<'de> for $N(&name) { + $C(method) + } + })?); + for function in matching.functions(ir)? { + file = file.add_function(function); + } + } + + if utoipa { + let payloads: Vec<_> = variants.into_iter().map(|(_, payload)| payload).collect(); + file = file.add_code(build_utoipa_one_of_impl(&name, &payloads)?); + } + file.build() +} + +fn derives(extra: Option<&ExtraDeriveConfig>, raw: bool, utoipa: bool) -> AnnotationSpec { + let mut names = vec!["Debug", "Clone", "Serialize"]; + if raw { + names.push("Deserialize"); + } + if let Some(extra) = extra { + for derive in &extra.derives { + // Serde traits are owned here, whether requested with their short + // name or fully qualified. In typed mode Deserialize is manual. + let path = derive.strip_prefix("::").unwrap_or(derive); + let canonical = path.strip_prefix("serde::").unwrap_or(path); + if canonical == "Deserialize" || names.contains(&canonical) { + continue; + } + if utoipa && matches!(path, "utoipa::ToSchema" | "ToSchema") { + // The integration owns a manual schema implementation for + // typed unions and raw wrappers alike. + continue; + } + names.push(derive); + } + } + AnnotationSpec::new("derive").args(names) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ir::types::{IrPrimitive, IrSchemaKind, IrTypeExpr}; + use sigil_stitch::assert_rendered; + + fn fixture() -> IrSpec { + crate::ir::lower::lower( + crate::parser::parse_content_yaml(include_str!( + "../../../../tests/fixtures/valid/union-selection.yaml" + )) + .unwrap(), + ) + .unwrap() + } + + #[test] + fn serde_derives_do_not_conflict_with_the_manual_trait_implementation() { + let extra = ExtraDeriveConfig { + derives: vec![ + "Deserialize".into(), + "serde::Deserialize".into(), + "serde::Serialize".into(), + "::serde::Deserialize".into(), + "::serde::Serialize".into(), + "utoipa::ToSchema".into(), + "::utoipa::ToSchema".into(), + "PartialEq".into(), + ], + ..Default::default() + }; + assert_rendered!( + Rust::new(), + derives(Some(&extra), false, true) + .emit(&Rust::new()) + .unwrap(), + "#[derive(Debug, Clone, Serialize, PartialEq)]\n", + ); + assert_rendered!( + Rust::new(), + derives(Some(&extra), true, true) + .emit(&Rust::new()) + .unwrap(), + "#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]\n", + ); + } + + #[test] + fn nested_finite_metadata_keeps_the_inner_null_exclusion() { + let mut ir = fixture(); + let inner = IrTypeExpr::ScalarEnum { + representation: Box::new(IrTypeExpr::Nullable(Box::new(IrTypeExpr::Primitive( + IrPrimitive::Integer, + )))), + representation_is_constraint: true, + representation_nullable: false, + values: vec![1.into()], + }; + let outer = IrTypeExpr::ScalarEnum { + representation: Box::new(inner), + representation_is_constraint: true, + representation_nullable: false, + values: vec![1.into(), serde_json::Value::Null], + }; + let IrSchemaKind::Object(object) = &mut ir.schemas.get_mut("NumericOne").unwrap().kind + else { + panic!("numeric member"); + }; + object.properties.get_mut("revision").unwrap().type_expr = outer; + let members = plan_union_members( + "Choice", + match &ir.schemas["Choice"].kind { + IrSchemaKind::Union(union) => union, + _ => panic!("untagged choice"), + }, + &ir, + ); + let crate::generators::union_match::JsonShape::Object(properties) = &members[0].shape + else { + panic!("object guard"); + }; + // The inner finite guard remains a conjunct, not merely the outer + // primitive representation plus its broader [1, null] enum. + let revision = &properties + .iter() + .find(|property| property.wire_name == "revision") + .unwrap() + .shape; + let crate::generators::union_match::JsonShape::AllOf(shapes) = revision else { + panic!("nested constraints remain intersected"); + }; + assert!( + shapes.contains(&crate::generators::union_match::JsonShape::Literal( + 1.into() + )) + ); + } +} diff --git a/src/generators/union_match.rs b/src/generators/union_match.rs index bb72beaa9..08c81e4a0 100644 --- a/src/generators/union_match.rs +++ b/src/generators/union_match.rs @@ -62,6 +62,8 @@ pub(crate) struct PropertyMatch { #[derive(Debug, Clone)] pub(crate) struct UnionMemberMatch { + /// Index of the authored member, before concrete/fallback partitioning. + pub source_index: usize, pub type_expr: IrTypeExpr, pub shape: JsonShape, pub fallback: bool, @@ -90,7 +92,8 @@ fn plan_members( ) -> Vec { let mut members: Vec<_> = types .iter() - .flat_map(|type_expr| { + .enumerate() + .flat_map(|(source_index, type_expr)| { let mut visiting = current_schema .map(|name| HashMap::from([(name.to_string(), 0)])) .unwrap_or_default(); @@ -100,6 +103,7 @@ fn plan_members( .into_iter() .filter_map(move |(shape, fallback)| { (shape != JsonShape::Never).then(|| UnionMemberMatch { + source_index, type_expr: type_expr.clone(), shape, fallback, @@ -734,8 +738,10 @@ mod tests { }; let members = plan_union_members("FallbackUnion", &union, &ir); assert!(!members[0].fallback); + assert_eq!(members[0].source_index, 1); assert!(matches!(&members[0].type_expr, IrTypeExpr::Named(name) if name == "ClientTool")); assert!(members[1].fallback); + assert_eq!(members[1].source_index, 0); } #[test] diff --git a/tests/fixtures/valid/rust-union-policies.yaml b/tests/fixtures/valid/rust-union-policies.yaml new file mode 100644 index 000000000..e9038cea1 --- /dev/null +++ b/tests/fixtures/valid/rust-union-policies.yaml @@ -0,0 +1,194 @@ +openapi: 3.1.0 +info: {title: Rust 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: + 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/Alpha'} + OpenMap: + type: object + additionalProperties: {$ref: '#/components/schemas/OpaqueFirst'} + MapOuter: + anyOf: + - {} + - {$ref: '#/components/schemas/OpenMap'} + 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]}} + first_value: {type: string} + ContainerTwo: + type: object + required: [values] + properties: + values: {type: array, items: {type: integer, enum: [2]}} + second_value: {type: string} + ContainerChoice: + oneOf: + - {$ref: '#/components/schemas/ContainerOne'} + - {$ref: '#/components/schemas/ContainerTwo'} + MapOne: + type: object + additionalProperties: {type: integer, enum: [1]} + MapTwo: + type: object + additionalProperties: {type: integer, enum: [2]} + MapChoice: + oneOf: + - {$ref: '#/components/schemas/MapOne'} + - {$ref: '#/components/schemas/MapTwo'} + LargeOne: + type: object + required: [revision] + properties: + revision: {type: integer, format: uint64, enum: [9007199254740992]} + LargeTwo: + type: object + required: [revision] + properties: + revision: {type: integer, format: uint64, enum: [9007199254740993, 18446744073709551615]} + 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/FiniteOne'} + - {$ref: '#/components/schemas/FiniteTwo'} + - {type: 'null'} + NullableEnvelope: + type: object + required: [choice] + properties: + choice: + anyOf: + - {$ref: '#/components/schemas/NullableChoice'} + - {type: 'null'} + BranchOne: + type: object + required: [revision, children] + properties: + revision: {type: integer, enum: [1]} + children: {type: array, items: {$ref: '#/components/schemas/Tree'}} + BranchTwo: + type: object + required: [revision, children] + properties: + revision: {type: integer, enum: [2]} + children: {type: array, items: {$ref: '#/components/schemas/Tree'}} + second_value: {type: string} + Tree: + oneOf: + - {$ref: '#/components/schemas/BranchOne'} + - {$ref: '#/components/schemas/BranchTwo'} + OpenBranch: + type: object + required: [wire_name, children] + properties: + wire_name: {type: string} + children: {type: array, items: {$ref: '#/components/schemas/OpenTree'}} + OpenTree: + anyOf: + - {$ref: '#/components/schemas/OpenBranch'} + - {} + OpenTreeMap: + type: object + additionalProperties: {$ref: '#/components/schemas/OpenTree'} + RecursiveMapOuter: + anyOf: + - {} + - {$ref: '#/components/schemas/OpenTreeMap'} + BinaryChoice: + oneOf: + - {type: string, format: binary} + - {type: string} + ScalarChoice: + oneOf: + - {type: integer, enum: [1]} + - {type: number, enum: [2.5]} + - {type: boolean, enum: [true]} + OptionalOpaque: + type: object + properties: + metadata: {} + OptionalOpaqueChoice: + anyOf: + - {$ref: '#/components/schemas/OptionalOpaque'} + - {type: string} diff --git a/tests/fixtures/valid/union-selection.yaml b/tests/fixtures/valid/union-selection.yaml new file mode 100644 index 000000000..309415cbd --- /dev/null +++ b/tests/fixtures/valid/union-selection.yaml @@ -0,0 +1,66 @@ +openapi: 3.1.0 +info: {title: Union selection contract, version: 1.0.0} +paths: {} +components: + schemas: + ClientTool: + type: object + required: [name, input_schema] + properties: + name: {type: string} + input_schema: {type: object, additionalProperties: true} + SearchTool: + type: object + required: [type, name] + properties: + type: {type: string, enum: [web_search_20250305]} + name: {type: string, enum: [web_search]} + max_uses: {type: integer} + Tool: + oneOf: + - {$ref: '#/components/schemas/ClientTool'} + - {$ref: '#/components/schemas/SearchTool'} + Request: + type: object + required: [tools] + properties: + tools: {type: array, items: {$ref: '#/components/schemas/Tool'}} + NumericOne: + type: object + required: [revision] + properties: + revision: {type: integer, enum: [1]} + first_value: {type: string} + NumericTwo: + type: object + required: [revision] + properties: + revision: {type: integer, enum: [2]} + second_value: {type: string} + Choice: + oneOf: + - {$ref: '#/components/schemas/NumericOne'} + - {$ref: '#/components/schemas/NumericTwo'} + RequiredOne: + type: object + required: [revision, first_value] + properties: + revision: {type: integer, enum: [1]} + first_value: {type: string} + RequiredTwo: + type: object + required: [revision, second_value] + properties: + revision: {type: integer, enum: [2]} + second_value: {type: string} + RequiredChoice: + oneOf: + - {$ref: '#/components/schemas/RequiredOne'} + - {$ref: '#/components/schemas/RequiredTwo'} + Envelope: + type: object + required: [choice, choice_list, choice_map] + properties: + choice: {$ref: '#/components/schemas/Choice'} + choice_list: {type: array, items: {$ref: '#/components/schemas/Choice'}} + choice_map: {type: object, additionalProperties: {$ref: '#/components/schemas/Choice'}} diff --git a/tests/generated-clients/rust/aioduct/Cargo.lock b/tests/generated-clients/rust/aioduct/Cargo.lock index 144b04c85..d8854bcae 100644 --- a/tests/generated-clients/rust/aioduct/Cargo.lock +++ b/tests/generated-clients/rust/aioduct/Cargo.lock @@ -41,8 +41,14 @@ dependencies = [ "aioduct", "nested-nullable-api", "rust-error-payload-shapes-api", + "rust-union-first-match", + "rust-union-raw", + "rust-union-strict", + "serde_json", "tokio", "typed-error-responses-api", + "union-selection-contract", + "utoipa", ] [[package]] @@ -494,6 +500,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -794,6 +802,42 @@ dependencies = [ "serde_repr", ] +[[package]] +name = "rust-union-first-match" +version = "0.1.0" +dependencies = [ + "aioduct", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + +[[package]] +name = "rust-union-raw" +version = "0.1.0" +dependencies = [ + "aioduct", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + +[[package]] +name = "rust-union-strict" +version = "0.1.0" +dependencies = [ + "aioduct", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -1175,6 +1219,17 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" +[[package]] +name = "union-selection-contract" +version = "0.1.0" +dependencies = [ + "aioduct", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -1199,6 +1254,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "want" version = "0.3.2" diff --git a/tests/generated-clients/rust/aioduct/Cargo.toml b/tests/generated-clients/rust/aioduct/Cargo.toml index 7abe3cf8c..1c12326c1 100644 --- a/tests/generated-clients/rust/aioduct/Cargo.toml +++ b/tests/generated-clients/rust/aioduct/Cargo.toml @@ -11,5 +11,11 @@ resolver = "3" typed-error-responses-api = { path = "generated/typed-error-responses" } rust-error-payload-shapes-api = { path = "generated/rust-error-payload-shapes" } nested-nullable-api = { path = "generated/nested-nullable" } +union-selection-contract = { path = "generated/union-selection" } +rust-union-first-match = { path = "generated/union-first-match" } +rust-union-strict = { path = "generated/union-strict" } +rust-union-raw = { path = "generated/union-raw" } +utoipa = "5" +serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } aioduct = { version = "0.2", features = ["tokio", "rustls", "rustls-ring", "json"] } diff --git a/tests/generated-clients/rust/aioduct/tests/union_http.rs b/tests/generated-clients/rust/aioduct/tests/union_http.rs new file mode 100644 index 000000000..80d512546 --- /dev/null +++ b/tests/generated-clients/rust/aioduct/tests/union_http.rs @@ -0,0 +1,17 @@ +use rust_union_first_match::apis::{GetChoiceError, GetChoiceResponse, SelectionApi}; +use rust_union_first_match::runtime::client::Client; + +#[path = "../../support/one_response_server.rs"] +mod server; + +#[path = "../../support/union_http.rs"] +mod cases; + +async fn request(status: &str, body: &'static str) -> Result { + let url = server::spawn_one_response_server(status, body); + let client = + Client::::new( + &url, + ); + SelectionApi::new(&client).get_choice().await +} diff --git a/tests/generated-clients/rust/aioduct/tests/union_policies.rs b/tests/generated-clients/rust/aioduct/tests/union_policies.rs new file mode 100644 index 000000000..43b375178 --- /dev/null +++ b/tests/generated-clients/rust/aioduct/tests/union_policies.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_policies.rs"] +mod cases; diff --git a/tests/generated-clients/rust/aioduct/tests/union_selection.rs b/tests/generated-clients/rust/aioduct/tests/union_selection.rs new file mode 100644 index 000000000..94675a32a --- /dev/null +++ b/tests/generated-clients/rust/aioduct/tests/union_selection.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_selection.rs"] +mod cases; diff --git a/tests/generated-clients/rust/reqwest/Cargo.lock b/tests/generated-clients/rust/reqwest/Cargo.lock index 8ebe03f3d..2ede3a3b9 100644 --- a/tests/generated-clients/rust/reqwest/Cargo.lock +++ b/tests/generated-clients/rust/reqwest/Cargo.lock @@ -490,6 +490,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -754,10 +756,16 @@ version = "0.0.0" dependencies = [ "nested-nullable-api", "rust-error-payload-shapes-api", + "rust-union-first-match", + "rust-union-raw", + "rust-union-strict", + "serde_json", "snafu", "thiserror", "tokio", "typed-error-responses-api", + "union-selection-contract", + "utoipa", ] [[package]] @@ -785,6 +793,42 @@ dependencies = [ "serde_repr", ] +[[package]] +name = "rust-union-first-match" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + +[[package]] +name = "rust-union-raw" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + +[[package]] +name = "rust-union-strict" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "utoipa", +] + [[package]] name = "rustix" version = "1.1.5" @@ -1285,6 +1329,17 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" +[[package]] +name = "union-selection-contract" +version = "0.1.0" +dependencies = [ + "reqwest", + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -1309,6 +1364,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/tests/generated-clients/rust/reqwest/Cargo.toml b/tests/generated-clients/rust/reqwest/Cargo.toml index fcae4bc9b..040e70189 100644 --- a/tests/generated-clients/rust/reqwest/Cargo.toml +++ b/tests/generated-clients/rust/reqwest/Cargo.toml @@ -11,6 +11,12 @@ resolver = "3" typed-error-responses-api = { path = "generated/typed-error-responses" } rust-error-payload-shapes-api = { path = "generated/rust-error-payload-shapes" } nested-nullable-api = { path = "generated/nested-nullable" } +union-selection-contract = { path = "generated/union-selection" } +rust-union-first-match = { path = "generated/union-first-match" } +rust-union-strict = { path = "generated/union-strict" } +rust-union-raw = { path = "generated/union-raw" } +utoipa = "5" +serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } thiserror = "2" snafu = "0.9.1" diff --git a/tests/generated-clients/rust/reqwest/tests/union_http.rs b/tests/generated-clients/rust/reqwest/tests/union_http.rs new file mode 100644 index 000000000..0c2bb7b5f --- /dev/null +++ b/tests/generated-clients/rust/reqwest/tests/union_http.rs @@ -0,0 +1,14 @@ +use rust_union_first_match::apis::{GetChoiceError, GetChoiceResponse, SelectionApi}; +use rust_union_first_match::runtime::client::Client; + +#[path = "../../support/one_response_server.rs"] +mod server; + +#[path = "../../support/union_http.rs"] +mod cases; + +async fn request(status: &str, body: &'static str) -> Result { + let url = server::spawn_one_response_server(status, body); + let client = Client::new(&url); + SelectionApi::new(&client).get_choice().await +} diff --git a/tests/generated-clients/rust/reqwest/tests/union_policies.rs b/tests/generated-clients/rust/reqwest/tests/union_policies.rs new file mode 100644 index 000000000..43b375178 --- /dev/null +++ b/tests/generated-clients/rust/reqwest/tests/union_policies.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_policies.rs"] +mod cases; diff --git a/tests/generated-clients/rust/reqwest/tests/union_selection.rs b/tests/generated-clients/rust/reqwest/tests/union_selection.rs new file mode 100644 index 000000000..94675a32a --- /dev/null +++ b/tests/generated-clients/rust/reqwest/tests/union_selection.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_selection.rs"] +mod cases; diff --git a/tests/generated-clients/rust/support/union_http.rs b/tests/generated-clients/rust/support/union_http.rs new file mode 100644 index 000000000..fd1e57665 --- /dev/null +++ b/tests/generated-clients/rust/support/union_http.rs @@ -0,0 +1,54 @@ +use super::request; +use rust_union_first_match::apis::GetChoiceError; +use rust_union_first_match::models::FiniteChoice; +use rust_union_first_match::runtime::error::{ApiCallError, Error}; + +const BODY: &str = + r#"{"revision":2,"values":[2.5],"flags":{"keep_key":true},"second_value":"two"}"#; + +#[tokio::test] +async fn success_response_selects_the_finite_member_without_losing_its_payload() { + let response = request("200 OK", BODY).await.unwrap(); + let value = response.data.unwrap(); + assert!(matches!(value, FiniteChoice::FiniteTwo(_))); + assert_eq!( + serde_json::to_value(value).unwrap(), + serde_json::from_str::(BODY).unwrap() + ); +} + +#[tokio::test] +async fn error_response_caches_the_same_correctly_selected_member() { + let error = request("400 Bad Request", BODY).await.unwrap_err(); + let GetChoiceError::BadRequest(error) = error else { + panic!("typed 400"); + }; + let first = error.body().unwrap(); + let second = error.body().unwrap(); + assert!(std::ptr::eq(first, second)); + assert!(matches!(first, FiniteChoice::FiniteTwo(_))); + assert_eq!( + serde_json::to_value(first).unwrap(), + serde_json::from_str::(BODY).unwrap() + ); + assert_eq!(error.status_code(), 400); + assert_eq!(error.raw_body(), BODY.as_bytes()); + assert_eq!( + error.headers().get("x-trace-id").unwrap().to_str().unwrap(), + "typed-error-test" + ); +} + +#[tokio::test] +async fn unmatched_error_payload_preserves_raw_http_metadata_and_decode_failure() { + let body = r#"{"revision":3,"values":[2.5],"flags":{"keep_key":true}}"#; + let error = request("400 Bad Request", body).await.unwrap_err(); + let GetChoiceError::BadRequest(ref typed) = error else { + panic!("typed 400"); + }; + assert!(matches!(typed.body(), Err(Error::Deserialize(_)))); + let uniform: ApiCallError = error.into(); + assert_eq!(uniform.operation_id(), "getChoice"); + assert_eq!(uniform.status_code(), Some(400)); + assert_eq!(uniform.raw_body(), Some(body.as_bytes())); +} diff --git a/tests/generated-clients/rust/support/union_policies.rs b/tests/generated-clients/rust/support/union_policies.rs new file mode 100644 index 000000000..b8ae7efd2 --- /dev/null +++ b/tests/generated-clients/rust/support/union_policies.rs @@ -0,0 +1,212 @@ +use serde_json::{Value, json}; + +fn finite_wire() -> Value { + json!({"revision": 2, "values": [2, 2.5], "flags": {"keep_key": true}, "second_value": "two"}) +} + +// These are ordinary in-process tests compiled once against each independently +// generated policy crate. No generation or runner orchestration happens here. +macro_rules! typed_cases { + ($module:ident, $client:ident) => { + mod $module { + use super::*; + use $client::models::*; + + #[test] + fn finite_scalar_array_map_facts_select_and_preserve_the_member() { + let value: FiniteChoice = serde_json::from_value(finite_wire()).unwrap(); + assert!(matches!(value, FiniteChoice::FiniteTwo(_))); + let expected = json!({"revision": 2, "values": [2.0, 2.5], "flags": {"keep_key": true}, "second_value": "two"}); + assert_eq!(serde_json::to_value(value).unwrap(), expected); + for invalid in [ + json!({"revision": 3, "values": [2.5], "flags": {"key": true}}), + json!({"revision": 2, "values": [1.5], "flags": {"key": true}}), + json!({"revision": 2, "values": [2.5], "flags": {"key": false}}), + json!({"revision": true, "values": [2.5], "flags": {"key": true}}), + ] { + assert!(serde_json::from_value::(invalid).is_err()); + } + } + + #[test] + fn container_constraints_distinguish_members_without_a_scalar_tag() { + let wire = json!({"values": [2, 2], "second_value": "two"}); + let value: ContainerChoice = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, ContainerChoice::ContainerTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + let wire = json!({"keep_key": 2}); + let value: MapChoice = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, MapChoice::MapTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + } + + #[test] + fn integers_above_double_precision_do_not_collapse_enum_members() { + for revision in [9007199254740993_u64, u64::MAX] { + let wire = json!({"revision": revision, "second_value": "two"}); + let value: LargeChoice = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, LargeChoice::LargeTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + } + } + + #[test] + fn scalar_unions_keep_numeric_boolean_and_finite_boundaries() { + assert!(matches!(serde_json::from_value::(json!(1)).unwrap(), ScalarChoice::Integer(1))); + assert!(matches!(serde_json::from_value::(json!(2.5)).unwrap(), ScalarChoice::Number(value) if value == 2.5)); + assert!(matches!(serde_json::from_value::(json!(true)).unwrap(), ScalarChoice::Boolean(true))); + for invalid in [json!(2), json!(false), json!("1"), Value::Null] { + assert!(serde_json::from_value::(invalid).is_err()); + } + } + + #[test] + fn finite_wrapper_null_permissions_are_not_widened() { + let value: NullableFields = serde_json::from_value(json!({"revision": null, "second_value": "two"})).unwrap(); + assert!(matches!(value, NullableFields::IncludedNull(_))); + let value: NullableFields = serde_json::from_value(json!({"revision": 1})).unwrap(); + assert!(matches!(value, NullableFields::ExcludedNull(_))); + } + + #[test] + fn nullable_use_sites_do_not_add_a_public_enum_variant() { + let envelope: NullableEnvelope = serde_json::from_value(json!({"choice": null})).unwrap(); + assert!(envelope.choice.is_none()); + // The typed enum itself has no synthetic Null/Raw variant. + assert!(serde_json::from_value::(Value::Null).is_err()); + assert!(serde_json::from_value::>(Value::Null).unwrap().is_none()); + } + + #[test] + fn opaque_fallbacks_do_not_hide_concrete_members_or_map_values() { + let value: OpaqueFirst = serde_json::from_value(json!({"wire_name": "detail"})).unwrap(); + assert!(matches!(value, OpaqueFirst::Alpha(_))); + let wire = json!({"keep_key": {"wire_name": "detail"}, "opaque": 42}); + let value: MapOuter = serde_json::from_value(wire.clone()).unwrap(); + let MapOuter::OpenMap(map) = &value else { panic!("concrete map wins"); }; + assert!(matches!(map.additional_properties["keep_key"], OpaqueFirst::Alpha(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + let value: OpaqueFirst = serde_json::from_value(json!(42)).unwrap(); + assert_eq!(serde_json::to_value(value).unwrap(), json!(42)); + } + + #[test] + fn recursive_selection_checks_deep_members_and_preserves_payloads() { + let wire = json!({"revision": 1, "children": [{"revision": 2, "children": [], "second_value": "leaf"}]}); + let value: Tree = serde_json::from_value(wire.clone()).unwrap(); + let Tree::BranchOne(branch) = &value else { panic!("root one"); }; + assert!(matches!(branch.children[0], Tree::BranchTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + assert!(serde_json::from_value::(json!({"revision": 1, "children": [{"revision": 3, "children": []}]})).is_err()); + } + + #[test] + fn deep_recursive_selection_preserves_payloads_and_rejects_unmatched_leaves() { + // Both members inspect the same children before their disjoint + // revision field. Reuse recursive matches, including failures. + let mut wire = json!({"revision": 2, "children": [], "second_value": "leaf"}); + let mut invalid = json!({"revision": 3, "children": []}); + for _ in 0..24 { + wire = json!({"revision": 2, "children": [wire], "second_value": "branch"}); + invalid = json!({"revision": 2, "children": [invalid]}); + } + let value: Tree = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, Tree::BranchTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + assert!(serde_json::from_value::(invalid).is_err()); + } + + #[test] + fn recursive_open_members_retain_both_concrete_and_raw_paths() { + let wire = json!({"keep_key": {"wire_name": "root", "children": [42, {"wire_name": "leaf", "children": []}]}, "opaque": false}); + let value: RecursiveMapOuter = serde_json::from_value(wire.clone()).unwrap(); + let RecursiveMapOuter::OpenTreeMap(map) = &value else { panic!("concrete map wins"); }; + let OpenTree::OpenBranch(branch) = &map.additional_properties["keep_key"] else { panic!("concrete root"); }; + assert!(matches!(branch.children[1], OpenTree::OpenBranch(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + let wire = json!({"opaque": false}); + let value: RecursiveMapOuter = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, RecursiveMapOuter::Variant0(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + } + + #[test] + fn binary_members_match_their_native_byte_array_wire_representation() { + assert!(matches!(serde_json::from_value::(json!([0, 255])).unwrap(), BinaryChoice::Binary(_))); + assert!(matches!(serde_json::from_value::(json!("text")).unwrap(), BinaryChoice::String(_))); + assert!(serde_json::from_value::(json!([256])).is_err()); + } + + #[test] + fn extra_derives_and_utoipa_work_with_manual_deserialization() { + let first: ScalarChoice = serde_json::from_value(json!(true)).unwrap(); + let second = first.clone(); + assert_eq!(first, second); + assert_eq!(::name(), "FiniteChoice"); + let schema = serde_json::to_value(::schema()).unwrap(); + assert_eq!(schema["oneOf"].as_array().unwrap().len(), 2); + } + + #[test] + fn optional_opaque_properties_keep_object_shape_checks_warning_clean() { + let value: OptionalOpaqueChoice = serde_json::from_value(json!({})).unwrap(); + assert!(matches!(value, OptionalOpaqueChoice::OptionalOpaque(_))); + let value: OptionalOpaqueChoice = serde_json::from_value(json!("opaque")).unwrap(); + assert!(matches!(value, OptionalOpaqueChoice::String(_))); + assert!(serde_json::from_value::(json!([42])).is_err()); + } + } + }; +} + +typed_cases!(first_match, rust_union_first_match); +typed_cases!(strict, rust_union_strict); + +#[test] +fn first_match_keeps_schema_order_for_ambiguous_concrete_members() { + use rust_union_first_match::models::{Ambiguous, OpenNested}; + let wire = json!({"wire_name": "detail"}); + let value: Ambiguous = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value, Ambiguous::Alpha(_))); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + assert!(serde_json::from_value::(wire).is_ok()); +} + +#[test] +fn strict_ambiguity_and_nested_delegate_errors_do_not_fall_back_to_raw() { + use rust_union_strict::models::{Ambiguous, OpenNested}; + let wire = json!({"wire_name": "detail"}); + for error in [ + serde_json::from_value::(wire.clone()).unwrap_err(), + serde_json::from_value::(wire).unwrap_err(), + ] { + assert!( + error + .to_string() + .contains("ambiguous untagged union Ambiguous"), + "{error}" + ); + } +} + +#[test] +fn raw_keeps_known_ambiguous_unmatched_and_null_values_without_typed_dispatch() { + use rust_union_raw::models::{Ambiguous, FiniteChoice, ScalarChoice}; + for wire in [ + finite_wire(), + json!({"unmatched": [null, true]}), + Value::Null, + ] { + let value: FiniteChoice = serde_json::from_value(wire.clone()).unwrap(); + let FiniteChoice::Raw(raw) = &value; + assert_eq!(raw, &wire); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + } + let wire = json!({"wire_name": "detail", "second_value": "untouched"}); + let value: Ambiguous = serde_json::from_value(wire.clone()).unwrap(); + assert_eq!(serde_json::to_value(value).unwrap(), wire); + let value: ScalarChoice = serde_json::from_value(json!({"raw": 42})).unwrap(); + assert_eq!(value, value.clone()); + let schema = serde_json::to_value(::schema()).unwrap(); + assert_eq!(schema["oneOf"].as_array().unwrap().len(), 1); +} diff --git a/tests/generated-clients/rust/support/union_selection.rs b/tests/generated-clients/rust/support/union_selection.rs new file mode 100644 index 000000000..9d1663400 --- /dev/null +++ b/tests/generated-clients/rust/support/union_selection.rs @@ -0,0 +1,80 @@ +use serde_json::{Value, json}; +use union_selection_contract::models::{ + Choice, ClientTool, Envelope, NumericTwo, Request, RequiredChoice, SearchTool, Tool, +}; + +fn client_wire() -> Value { + json!({"name": "weather", "input_schema": {"type": "object"}}) +} + +fn search_wire() -> Value { + json!({"type": "web_search_20250305", "name": "web_search", "max_uses": 1}) +} + +fn numeric_wire() -> Value { + json!({"revision": 2, "second_value": "two"}) +} + +#[test] +fn direct_tool_members_preserve_wire() { + let client: ClientTool = serde_json::from_value(client_wire()).unwrap(); + let search: SearchTool = serde_json::from_value(search_wire()).unwrap(); + assert_eq!(serde_json::to_value(client).unwrap(), client_wire()); + assert_eq!(serde_json::to_value(search).unwrap(), search_wire()); +} + +#[test] +fn typed_union_request_is_json_serializable() { + let request = Request { + tools: vec![ + Tool::ClientTool(serde_json::from_value(client_wire()).unwrap()), + Tool::SearchTool(serde_json::from_value(search_wire()).unwrap()), + ], + }; + assert_eq!( + serde_json::to_value(request).unwrap(), + json!({"tools": [client_wire(), search_wire()]}) + ); +} + +#[test] +fn inbound_tool_request_constructs_union_variants() { + let wire = json!({"tools": [client_wire(), search_wire()]}); + let request: Request = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(request.tools[0], Tool::ClientTool(_))); + assert!(matches!(request.tools[1], Tool::SearchTool(_))); + assert_eq!(serde_json::to_value(request).unwrap(), wire); +} + +#[test] +fn direct_numeric_member_preserves_payload() { + let value: NumericTwo = serde_json::from_value(numeric_wire()).unwrap(); + assert_eq!(serde_json::to_value(value).unwrap(), numeric_wire()); +} + +#[test] +fn disjoint_required_fields_select_second_member() { + let value: RequiredChoice = serde_json::from_value(numeric_wire()).unwrap(); + assert!(matches!(value, RequiredChoice::RequiredTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), numeric_wire()); +} + +#[test] +fn disjoint_numeric_enums_select_second_member() { + let value: Choice = serde_json::from_value(numeric_wire()).unwrap(); + assert!(matches!(value, Choice::NumericTwo(_))); + assert_eq!(serde_json::to_value(value).unwrap(), numeric_wire()); +} + +#[test] +fn nested_scalar_array_map_unions_preserve_payloads() { + let wire = json!({"choice": numeric_wire(), "choice_list": [numeric_wire()], "choice_map": {"keep_key": numeric_wire()}}); + let value: Envelope = serde_json::from_value(wire.clone()).unwrap(); + assert!(matches!(value.choice, Choice::NumericTwo(_))); + assert!(matches!(value.choice_list[0], Choice::NumericTwo(_))); + assert!(matches!( + value.choice_map["keep_key"], + Choice::NumericTwo(_) + )); + assert_eq!(serde_json::to_value(value).unwrap(), wire); +} diff --git a/tests/generated-clients/rust/union-policies.toml b/tests/generated-clients/rust/union-policies.toml new file mode 100644 index 000000000..6c624a5ec --- /dev/null +++ b/tests/generated-clients/rust/union-policies.toml @@ -0,0 +1,31 @@ +# Exercise the same policy options, extra derives, and utoipa integration in +# every transport. The generation entrypoint overrides only name and policy. +[rust-reqwest.utoipa] +enabled = true +dependency = '"5"' +[rust-reqwest.extra_derives.structs] +derives = ["PartialEq"] +[rust-reqwest.extra_derives.unions] +derives = ["PartialEq", "::serde::Deserialize", "::serde::Serialize", "::utoipa::ToSchema"] +[rust-reqwest.extra_derives.per_type.ScalarChoice] +derives = ["PartialEq", "Deserialize", "serde::Deserialize", "serde::Serialize", "::serde::Deserialize", "::serde::Serialize", "utoipa::ToSchema", "::utoipa::ToSchema"] + +[rust-ureq.utoipa] +enabled = true +dependency = '"5"' +[rust-ureq.extra_derives.structs] +derives = ["PartialEq"] +[rust-ureq.extra_derives.unions] +derives = ["PartialEq", "::serde::Deserialize", "::serde::Serialize", "::utoipa::ToSchema"] +[rust-ureq.extra_derives.per_type.ScalarChoice] +derives = ["PartialEq", "Deserialize", "serde::Deserialize", "serde::Serialize", "::serde::Deserialize", "::serde::Serialize", "utoipa::ToSchema", "::utoipa::ToSchema"] + +[rust-aioduct.utoipa] +enabled = true +dependency = '"5"' +[rust-aioduct.extra_derives.structs] +derives = ["PartialEq"] +[rust-aioduct.extra_derives.unions] +derives = ["PartialEq", "::serde::Deserialize", "::serde::Serialize", "::utoipa::ToSchema"] +[rust-aioduct.extra_derives.per_type.ScalarChoice] +derives = ["PartialEq", "Deserialize", "serde::Deserialize", "serde::Serialize", "::serde::Deserialize", "::serde::Serialize", "utoipa::ToSchema", "::utoipa::ToSchema"] diff --git a/tests/generated-clients/rust/ureq/Cargo.lock b/tests/generated-clients/rust/ureq/Cargo.lock index 487bed454..7aa42a3ab 100644 --- a/tests/generated-clients/rust/ureq/Cargo.lock +++ b/tests/generated-clients/rust/ureq/Cargo.lock @@ -88,7 +88,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -277,6 +277,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown", + "serde", + "serde_core", ] [[package]] @@ -418,6 +420,42 @@ dependencies = [ "ureq", ] +[[package]] +name = "rust-union-first-match" +version = "0.1.0" +dependencies = [ + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "ureq", + "utoipa", +] + +[[package]] +name = "rust-union-raw" +version = "0.1.0" +dependencies = [ + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "ureq", + "utoipa", +] + +[[package]] +name = "rust-union-strict" +version = "0.1.0" +dependencies = [ + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "ureq", + "utoipa", +] + [[package]] name = "rustls" version = "0.23.46" @@ -492,7 +530,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -516,7 +554,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -549,6 +587,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "3.0.9" @@ -568,7 +617,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -588,7 +637,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -649,7 +698,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -669,6 +718,17 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" +[[package]] +name = "union-selection-contract" +version = "0.1.0" +dependencies = [ + "serde", + "serde-xml-rs", + "serde_json", + "serde_repr", + "ureq", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -701,8 +761,14 @@ version = "0.0.0" dependencies = [ "nested-nullable-api", "rust-error-payload-shapes-api", + "rust-union-first-match", + "rust-union-raw", + "rust-union-strict", + "serde_json", "tokio", "typed-error-responses-api", + "union-selection-contract", + "utoipa", ] [[package]] @@ -741,6 +807,29 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "version_check" version = "0.9.5" @@ -866,7 +955,7 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] @@ -887,7 +976,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] @@ -927,7 +1016,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] diff --git a/tests/generated-clients/rust/ureq/Cargo.toml b/tests/generated-clients/rust/ureq/Cargo.toml index 2f0cb3e3d..011392e56 100644 --- a/tests/generated-clients/rust/ureq/Cargo.toml +++ b/tests/generated-clients/rust/ureq/Cargo.toml @@ -11,4 +11,10 @@ resolver = "3" typed-error-responses-api = { path = "generated/typed-error-responses" } rust-error-payload-shapes-api = { path = "generated/rust-error-payload-shapes" } nested-nullable-api = { path = "generated/nested-nullable" } +union-selection-contract = { path = "generated/union-selection" } +rust-union-first-match = { path = "generated/union-first-match" } +rust-union-strict = { path = "generated/union-strict" } +rust-union-raw = { path = "generated/union-raw" } +utoipa = "5" +serde_json = "1" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/tests/generated-clients/rust/ureq/tests/union_http.rs b/tests/generated-clients/rust/ureq/tests/union_http.rs new file mode 100644 index 000000000..92cc7ab04 --- /dev/null +++ b/tests/generated-clients/rust/ureq/tests/union_http.rs @@ -0,0 +1,14 @@ +use rust_union_first_match::apis::{GetChoiceError, GetChoiceResponse, SelectionApi}; +use rust_union_first_match::runtime::client::Client; + +#[path = "../../support/one_response_server.rs"] +mod server; + +#[path = "../../support/union_http.rs"] +mod cases; + +async fn request(status: &str, body: &'static str) -> Result { + let url = server::spawn_one_response_server(status, body); + let client = Client::new(&url); + SelectionApi::new(&client).get_choice() +} diff --git a/tests/generated-clients/rust/ureq/tests/union_policies.rs b/tests/generated-clients/rust/ureq/tests/union_policies.rs new file mode 100644 index 000000000..43b375178 --- /dev/null +++ b/tests/generated-clients/rust/ureq/tests/union_policies.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_policies.rs"] +mod cases; diff --git a/tests/generated-clients/rust/ureq/tests/union_selection.rs b/tests/generated-clients/rust/ureq/tests/union_selection.rs new file mode 100644 index 000000000..94675a32a --- /dev/null +++ b/tests/generated-clients/rust/ureq/tests/union_selection.rs @@ -0,0 +1,2 @@ +#[path = "../../support/union_selection.rs"] +mod cases; diff --git a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/any_of_types.rs.golden b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/any_of_types.rs.golden index 2289e3e8d..e69d0db37 100644 --- a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/any_of_types.rs.golden +++ b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/any_of_types.rs.golden @@ -4,12 +4,38 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Any of multiple types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum AnyOfTypes { String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for AnyOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union AnyOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_objects.rs.golden b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_objects.rs.golden index 698d2f540..b2913d01c 100644 --- a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_objects.rs.golden +++ b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_objects.rs.golden @@ -4,12 +4,98 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of object types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfObjects { BasicObject(super::BasicObject), NestedObject(super::NestedObject), } + +impl<'de> serde::Deserialize<'de> for OneOfObjects { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NestedObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfObjects")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("metadata").is_some_and(|item | !__nexus_matches_5(item)) { + return false; + } + if object.get("user").is_some_and(|item | !__nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("created").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if object.get("name").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_types.rs.golden b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_types.rs.golden index 966337bf0..e4a7ba0ba 100644 --- a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_types.rs.golden +++ b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_types.rs.golden @@ -4,13 +4,47 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of multiple primitive types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfTypes { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for OneOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_with_null.rs.golden b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_with_null.rs.golden index 0022a079c..0b133073b 100644 --- a/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_with_null.rs.golden +++ b/tests/golden/rust/rust-aioduct/comprehensive-schemas/src/models/one_of_with_null.rs.golden @@ -4,12 +4,59 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of string, null, and schema reference -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfWithNull { String(String), BasicObject(super::BasicObject), } + +impl<'de> serde::Deserialize<'de> for OneOfWithNull { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfWithNull")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_0(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} diff --git a/tests/golden/rust/rust-aioduct/enum-repr/src/models/mixed_enum.rs.golden b/tests/golden/rust/rust-aioduct/enum-repr/src/models/mixed_enum.rs.golden index fda4acb9f..6ba6f55c7 100644 --- a/tests/golden/rust/rust-aioduct/enum-repr/src/models/mixed_enum.rs.golden +++ b/tests/golden/rust/rust-aioduct/enum-repr/src/models/mixed_enum.rs.golden @@ -4,13 +4,13 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Mixed enum with unit variants (SimpleA, SimpleB) and tuple variants (VariantA(VariantA), VariantB(VariantB)) /// /// Serializes as externally tagged: `{"SimpleA": null}`, `{"VariantA": {"field1": "...", "field2": 123}}`, /// `{"SimpleB": null}`, `{"VariantB": {"field3": true, "field4": 1.23}}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum MixedEnum { Variant0(String), @@ -18,3 +18,103 @@ pub enum MixedEnum { Variant2(String), MixedEnumMember4(super::MixedEnumMember4), } + +impl<'de> serde::Deserialize<'de> for MixedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(3); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MixedEnumMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + Some(3) => serde_json::from_value::(candidate).map(Self::MixedEnumMember4).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MixedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleA") +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantA").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleB") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantB").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_8(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/enum-repr/src/models/untagged_enum.rs.golden b/tests/golden/rust/rust-aioduct/enum-repr/src/models/untagged_enum.rs.golden index c343d3697..ae57e3671 100644 --- a/tests/golden/rust/rust-aioduct/enum-repr/src/models/untagged_enum.rs.golden +++ b/tests/golden/rust/rust-aioduct/enum-repr/src/models/untagged_enum.rs.golden @@ -4,15 +4,77 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Untagged enum /// /// No tag is used, variants are distinguished by their structure: /// `{"field1": "...", "field2": 123}` or `{"field3": true, "field4": 1.23}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum UntaggedEnum { VariantA(super::VariantA), VariantB(super::VariantB), } + +impl<'de> serde::Deserialize<'de> for UntaggedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::VariantA).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::VariantB).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union UntaggedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-complex-union/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-complex-union/src/models/foo.rs.golden index 3161f5330..552765d6b 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-complex-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-complex-union/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Complex Union Type Test — 1.0.0 // Test fixture for complex union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/foo.rs.golden index b90626b8b..c51d61780 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/foo.rs.golden @@ -4,12 +4,88 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Qux(super::Qux), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_4(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/qux.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/qux.rs.golden index 25664e6de..733e088ca 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/qux.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-nested-union/src/models/qux.rs.golden @@ -4,11 +4,69 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Qux { Bar(super::Bar), Baz(super::Baz), } + +impl<'de> serde::Deserialize<'de> for Qux { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Qux")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-union-mixed/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-union-mixed/src/models/foo.rs.golden index dad6c77c7..66f7443a1 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-union-mixed/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-union-mixed/src/models/foo.rs.golden @@ -4,12 +4,60 @@ // Union Type with Mixed Members Test — 1.0.0 // Test fixture for union types with both interfaces and primitives -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-union-with-any/src/models/config.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-union-with-any/src/models/config.rs.golden index 4126397a6..ece07e1d4 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-union-with-any/src/models/config.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-union-with-any/src/models/config.rs.golden @@ -4,12 +4,42 @@ // Union Type with Any Test — 1.0.0 // Test fixture for union types that include the any type -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Config { String(String), Number(f64), Variant2(serde_json::Value), } + +impl<'de> serde::Deserialize<'de> for Config { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Config")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-union-with-inline-objects/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-union-with-inline-objects/src/models/foo.rs.golden index 630ac5283..bd92cb532 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-union-with-inline-objects/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-union-with-inline-objects/src/models/foo.rs.golden @@ -4,13 +4,108 @@ // Union Type with Inline Objects Test — 1.0.0 // Test fixture for union types (oneOf) with inline object schemas that should generate named interfaces -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union type with inline object schemas -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { FooMember1(super::FooMember1), FooMember2(super::FooMember2), FooMember3(super::FooMember3), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FooMember1).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FooMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::FooMember3).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bar").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("baz").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("qux").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("quux").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("corge").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("grault").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("garply").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-union-with-interfaces/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-union-with-interfaces/src/models/foo.rs.golden index f49bad848..127fd1d4c 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-union-with-interfaces/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-union-with-interfaces/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Union Type with Interfaces Test — 1.0.0 // Test fixture for oneOf/anyOf union types with interface members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-aioduct/type-aliases-union-with-primitives/src/models/foo.rs.golden b/tests/golden/rust/rust-aioduct/type-aliases-union-with-primitives/src/models/foo.rs.golden index fc723f323..3a977057a 100644 --- a/tests/golden/rust/rust-aioduct/type-aliases-union-with-primitives/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-aioduct/type-aliases-union-with-primitives/src/models/foo.rs.golden @@ -4,12 +4,46 @@ // Union Type with Primitives Test — 1.0.0 // Test fixture for union types with primitive members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/union-first-match/Cargo.toml.golden new file mode 100644 index 000000000..ed830ba23 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-first-match" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +aioduct = { version = "0.2", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-aioduct/union-first-match/README.md.golden b/tests/golden/rust/rust-aioduct/union-first-match/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/apis/selection.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/apis/selection.rs.golden new file mode 100644 index 000000000..d44c39139 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> { + client: &'a Client, +} + +impl<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> SelectionApi<'a, R, C> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await.map_err(Error::from)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::from)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: aioduct::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/alpha.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..a629e03df --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous.rs.golden @@ -0,0 +1,78 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/beta.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..b840312d2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/binary_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_one.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_two.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_choice.rs.golden new file mode 100644 index 000000000..5c3ecf5ec --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_choice.rs.golden @@ -0,0 +1,126 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_one.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_two.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..a221aea9d --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_one.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_two.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/included_null.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_choice.rs.golden new file mode 100644 index 000000000..6b6af2c6e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_choice.rs.golden @@ -0,0 +1,133 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_one.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_two.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_choice.rs.golden new file mode 100644 index 000000000..9bfbfc5d2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_choice.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_one.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_outer.rs.golden new file mode 100644 index 000000000..6669192b8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_outer.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_two.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..d4d082c01 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..2b79fe48a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/nullable_fields.rs.golden @@ -0,0 +1,147 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..0569fef62 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/opaque_first.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_branch.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_map.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_nested.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_nested.rs.golden new file mode 100644 index 000000000..cba893366 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_nested.rs.golden @@ -0,0 +1,88 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree.rs.golden new file mode 100644 index 000000000..4662f2f73 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..7cefd61f5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..d39ae673a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,101 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..ee1cfae8a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/scalar_choice.rs.golden @@ -0,0 +1,161 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_8(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/models/tree.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/models/tree.rs.golden new file mode 100644 index 000000000..878d21316 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/models/tree.rs.golden @@ -0,0 +1,151 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate, memo) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/auth.rs.golden new file mode 100644 index 000000000..51202b94c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/auth.rs.golden @@ -0,0 +1,181 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: + Send + Sync + std::fmt::Debug +{ + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/client.rs.golden new file mode 100644 index 000000000..8dd705bb5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/client.rs.golden @@ -0,0 +1,111 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::HttpEngineSend`. +pub struct Client { + inner: aioduct::HttpEngineSend, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::HttpEngineSend::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } +} + +impl Client { + /// Create a client with a custom `aioduct::HttpEngineSend`. + pub fn with_client(inner: aioduct::HttpEngineSend, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/error.rs.golden new file mode 100644 index 000000000..9e570befa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/error.rs.golden @@ -0,0 +1,229 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: aioduct::SendError) -> Self { + Error::Http(e.into()) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &aioduct::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&aioduct::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-first-match/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/union-raw/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/union-raw/Cargo.toml.golden new file mode 100644 index 000000000..68aa52b59 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-raw" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +aioduct = { version = "0.2", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-aioduct/union-raw/README.md.golden b/tests/golden/rust/rust-aioduct/union-raw/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/apis/selection.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/apis/selection.rs.golden new file mode 100644 index 000000000..d44c39139 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> { + client: &'a Client, +} + +impl<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> SelectionApi<'a, R, C> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await.map_err(Error::from)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::from)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: aioduct::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/alpha.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..f3605f51f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/beta.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..e03496534 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/binary_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_one.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_two.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/container_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ef4eaf077 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/container_one.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/container_two.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..26e0713b3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_one.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_two.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/included_null.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/large_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_choice.rs.golden new file mode 100644 index 000000000..f140cdf4a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/large_one.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/large_two.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/map_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_choice.rs.golden new file mode 100644 index 000000000..a33365975 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/map_one.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_outer.rs.golden new file mode 100644 index 000000000..b6c35b0c0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/map_two.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..27f3859c0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..65f8d4382 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/nullable_fields.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..67baf789e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/opaque_first.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/open_branch.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/open_map.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/open_nested.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_nested.rs.golden new file mode 100644 index 000000000..e4aec32e9 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_nested.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree.rs.golden new file mode 100644 index 000000000..95b617a5e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..94079e0ac --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..f83d98409 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..288e1f70e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/scalar_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/models/tree.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/models/tree.rs.golden new file mode 100644 index 000000000..bc126a306 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/models/tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/auth.rs.golden new file mode 100644 index 000000000..51202b94c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/auth.rs.golden @@ -0,0 +1,181 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: + Send + Sync + std::fmt::Debug +{ + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/client.rs.golden new file mode 100644 index 000000000..8dd705bb5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/client.rs.golden @@ -0,0 +1,111 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::HttpEngineSend`. +pub struct Client { + inner: aioduct::HttpEngineSend, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::HttpEngineSend::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } +} + +impl Client { + /// Create a client with a custom `aioduct::HttpEngineSend`. + pub fn with_client(inner: aioduct::HttpEngineSend, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/error.rs.golden new file mode 100644 index 000000000..9e570befa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/error.rs.golden @@ -0,0 +1,229 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: aioduct::SendError) -> Self { + Error::Http(e.into()) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &aioduct::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&aioduct::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-aioduct/union-raw/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-raw/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/union-selection/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/union-selection/Cargo.toml.golden new file mode 100644 index 000000000..aa659c7bd --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "union-selection-contract" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +aioduct = { version = "0.2", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-aioduct/union-selection/README.md.golden b/tests/golden/rust/rust-aioduct/union-selection/README.md.golden new file mode 100644 index 000000000..681418c90 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/README.md.golden @@ -0,0 +1,7 @@ +# Union selection contract + +Generated Rust 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/rust/rust-aioduct/union-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..e69b0985f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/apis/mod.rs.golden @@ -0,0 +1,5 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/lib.rs.golden new file mode 100644 index 000000000..bea884b24 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/choice.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/choice.rs.golden new file mode 100644 index 000000000..932f64635 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Choice { + NumericOne(super::NumericOne), + NumericTwo(super::NumericTwo), +} + +impl<'de> serde::Deserialize<'de> for Choice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::NumericOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NumericTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Choice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/client_tool.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/client_tool.rs.golden new file mode 100644 index 000000000..3643ac933 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/client_tool.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientTool { + pub input_schema: std::collections::HashMap, + pub name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/envelope.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/envelope.rs.golden new file mode 100644 index 000000000..401e7e986 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/envelope.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Envelope { + pub choice: super::Choice, + pub choice_list: Vec, + pub choice_map: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..9f7247500 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/mod.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +mod choice; +pub use choice::*; +mod client_tool; +pub use client_tool::*; +mod envelope; +pub use envelope::*; +mod numeric_one; +pub use numeric_one::*; +mod numeric_two; +pub use numeric_two::*; +mod request; +pub use request::*; +mod required_choice; +pub use required_choice::*; +mod required_one; +pub use required_one::*; +mod required_two; +pub use required_two::*; +mod search_tool; +pub use search_tool::*; +mod tool; +pub use tool::*; diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_one.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_one.rs.golden new file mode 100644 index 000000000..9ebb0f3d4 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_two.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_two.rs.golden new file mode 100644 index 000000000..96f26a90f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/numeric_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericTwo { + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/request.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/request.rs.golden new file mode 100644 index 000000000..10498f58d --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/request.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Request { + pub tools: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/required_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_choice.rs.golden new file mode 100644 index 000000000..ac6ac5fb2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum RequiredChoice { + RequiredOne(super::RequiredOne), + RequiredTwo(super::RequiredTwo), +} + +impl<'de> serde::Deserialize<'de> for RequiredChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::RequiredOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::RequiredTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RequiredChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("first_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("second_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/required_one.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_one.rs.golden new file mode 100644 index 000000000..42d46bdb7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredOne { + pub first_value: String, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/required_two.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_two.rs.golden new file mode 100644 index 000000000..f364d79ee --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/required_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredTwo { + pub revision: i64, + pub second_value: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/search_tool.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/search_tool.rs.golden new file mode 100644 index 000000000..de1df9010 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/search_tool.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchTool { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub max_uses: Option, + pub name: String, + #[serde(rename = "type")] + pub r#type: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/models/tool.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/models/tool.rs.golden new file mode 100644 index 000000000..57924d7e8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/models/tool.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Tool { + ClientTool(super::ClientTool), + SearchTool(super::SearchTool), +} + +impl<'de> serde::Deserialize<'de> for Tool { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClientTool).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::SearchTool).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tool")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("input_schema").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("max_uses").is_some_and(|item | !__nexus_matches_4(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search_20250305") +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..18571b9b3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/auth.rs.golden @@ -0,0 +1,181 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: + Send + Sync + std::fmt::Debug +{ + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..bc3be774b --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/client.rs.golden @@ -0,0 +1,111 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::HttpEngineSend`. +pub struct Client { + inner: aioduct::HttpEngineSend, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::HttpEngineSend::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } +} + +impl Client { + /// Create a client with a custom `aioduct::HttpEngineSend`. + pub fn with_client(inner: aioduct::HttpEngineSend, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..3f65671a0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/error.rs.golden @@ -0,0 +1,106 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: aioduct::SendError) -> Self { + Error::Http(e.into()) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &aioduct::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} diff --git a/tests/golden/rust/rust-aioduct/union-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..d24426638 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-selection/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/union-strict/Cargo.toml.golden b/tests/golden/rust/rust-aioduct/union-strict/Cargo.toml.golden new file mode 100644 index 000000000..0c2874782 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-strict" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +aioduct = { version = "0.2", features = ["tokio", "rustls", "rustls-ring", "json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-aioduct/union-strict/README.md.golden b/tests/golden/rust/rust-aioduct/union-strict/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/apis/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/apis/selection.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/apis/selection.rs.golden new file mode 100644 index 000000000..d44c39139 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> { + client: &'a Client, +} + +impl<'a, R: aioduct::RuntimePoll, C: aioduct::ConnectorSend> SelectionApi<'a, R, C> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path)?; + let resp = req.send().await.map_err(Error::from)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::from)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: aioduct::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/lib.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/alpha.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..0f6de428f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous.rs.golden @@ -0,0 +1,84 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_2(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/beta.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..f52475aab --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/binary_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_one.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_two.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/container_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ec10517d3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_choice.rs.golden @@ -0,0 +1,132 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/container_one.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/container_two.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..8c89d4236 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_one.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_two.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/included_null.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/large_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_choice.rs.golden new file mode 100644 index 000000000..7cc03e122 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_choice.rs.golden @@ -0,0 +1,139 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/large_one.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/large_two.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/map_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_choice.rs.golden new file mode 100644 index 000000000..ca809ee6a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_choice.rs.golden @@ -0,0 +1,100 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/map_one.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_outer.rs.golden new file mode 100644 index 000000000..4ef317b79 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_outer.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/map_two.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..49efce185 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..a9feb9d8a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/nullable_fields.rs.golden @@ -0,0 +1,153 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..86edb5f4f --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/opaque_first.rs.golden @@ -0,0 +1,67 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpaqueFirst: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/open_branch.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/open_map.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/open_nested.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_nested.rs.golden new file mode 100644 index 000000000..1624f60c1 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_nested.rs.golden @@ -0,0 +1,91 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenNested: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree.rs.golden new file mode 100644 index 000000000..683b0a00e --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenTree: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..f046c904b --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..3b6110e96 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union RecursiveMapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..c8b6dac14 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/scalar_choice.rs.golden @@ -0,0 +1,170 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(1); + } + if __nexus_matches_8(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/models/tree.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/models/tree.rs.golden new file mode 100644 index 000000000..9a8533288 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/models/tree.rs.golden @@ -0,0 +1,157 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/runtime/auth.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/auth.rs.golden new file mode 100644 index 000000000..51202b94c --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/auth.rs.golden @@ -0,0 +1,181 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use crate::runtime::error::Error; + +/// Trait for authenticating requests. +pub trait Authenticator: + Send + Sync + std::fmt::Debug +{ + /// Apply authentication to the request builder. + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + Ok(req.bearer_auth(&token)) + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate<'a>( + &self, + req: aioduct::RequestBuilderSend<'a, R, C>, + ) -> Result, Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + Ok(req.header_str(&self.header_name, &key)?) + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/runtime/client.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/client.rs.golden new file mode 100644 index 000000000..8dd705bb5 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/client.rs.golden @@ -0,0 +1,111 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// Async HTTP client wrapping `aioduct::HttpEngineSend`. +pub struct Client { + inner: aioduct::HttpEngineSend, + base_url: String, + authenticator: Option>>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: aioduct::HttpEngineSend::::with_rustls(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } +} + +impl Client { + /// Create a client with a custom `aioduct::HttpEngineSend`. + pub fn with_client(inner: aioduct::HttpEngineSend, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc>) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> Result, Error> { + let mut req = self.inner.get(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> Result, Error> { + let mut req = self.inner.post(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> Result, Error> { + let mut req = self.inner.put(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> Result, Error> { + let mut req = self.inner.delete(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> Result, Error> { + let mut req = self.inner.patch(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> Result, Error> { + let mut req = self.inner.head(&self.full_url(path))?; + if let Some(auth) = &self.authenticator { + req = auth.authenticate(req)?; + } + Ok(req) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/runtime/error.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/error.rs.golden new file mode 100644 index 000000000..9e570befa --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/error.rs.golden @@ -0,0 +1,229 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP/transport error from aioduct. + Http(aioduct::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: aioduct::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: aioduct::SendError) -> Self { + Error::Http(e.into()) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &aioduct::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: aioduct::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&aioduct::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-aioduct/union-strict/src/runtime/mod.rs.golden b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-aioduct/union-strict/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/feed_source.rs.golden b/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/feed_source.rs.golden index 0cb0e3c98..e3babc3ec 100644 --- a/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/feed_source.rs.golden +++ b/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/feed_source.rs.golden @@ -25,13 +25,13 @@ pub enum FeedSource { impl utoipa::PartialSchema for FeedSource { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for FeedSource { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("FeedSource") diff --git a/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/payment_method.rs.golden b/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/payment_method.rs.golden index 15a5b804e..fa33cf9e4 100644 --- a/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/payment_method.rs.golden +++ b/tests/golden/rust/rust-aioduct/utoipa-mixed/src/models/payment_method.rs.golden @@ -4,25 +4,78 @@ // Utoipa Mixed Types Test — 1.0.0 // Test fixture covering all schema kinds with utoipa enabled -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// A payment method (card or bank transfer) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum PaymentMethod { CardPayment(super::CardPayment), BankTransfer(super::BankTransfer), } +impl<'de> serde::Deserialize<'de> for PaymentMethod { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::CardPayment).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BankTransfer).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union PaymentMethod")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("card_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("cvv").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("expiry").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("account_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("routing_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for PaymentMethod { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for PaymentMethod { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("PaymentMethod") diff --git a/tests/golden/rust/rust-aioduct/utoipa-untagged-union/src/models/event.rs.golden b/tests/golden/rust/rust-aioduct/utoipa-untagged-union/src/models/event.rs.golden index c934331fa..266096b04 100644 --- a/tests/golden/rust/rust-aioduct/utoipa-untagged-union/src/models/event.rs.golden +++ b/tests/golden/rust/rust-aioduct/utoipa-untagged-union/src/models/event.rs.golden @@ -4,10 +4,10 @@ // Utoipa Untagged Union Test — 1.0.0 // Test fixture for untagged union manual utoipa impl generation -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// An event payload (click, purchase, or page view) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Event { ClickEvent(super::ClickEvent), @@ -15,16 +15,97 @@ pub enum Event { PageViewEvent(super::PageViewEvent), } +impl<'de> serde::Deserialize<'de> for Event { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClickEvent).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::PurchaseEvent).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::PageViewEvent).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Event")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("element_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("x").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("y").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("amount").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("currency").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("item_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("referrer").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for Event { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for Event { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("Event") diff --git a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/any_of_types.rs.golden b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/any_of_types.rs.golden index 2289e3e8d..e69d0db37 100644 --- a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/any_of_types.rs.golden +++ b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/any_of_types.rs.golden @@ -4,12 +4,38 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Any of multiple types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum AnyOfTypes { String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for AnyOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union AnyOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_objects.rs.golden b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_objects.rs.golden index 698d2f540..b2913d01c 100644 --- a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_objects.rs.golden +++ b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_objects.rs.golden @@ -4,12 +4,98 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of object types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfObjects { BasicObject(super::BasicObject), NestedObject(super::NestedObject), } + +impl<'de> serde::Deserialize<'de> for OneOfObjects { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NestedObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfObjects")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("metadata").is_some_and(|item | !__nexus_matches_5(item)) { + return false; + } + if object.get("user").is_some_and(|item | !__nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("created").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if object.get("name").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_types.rs.golden b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_types.rs.golden index 966337bf0..e4a7ba0ba 100644 --- a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_types.rs.golden +++ b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_types.rs.golden @@ -4,13 +4,47 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of multiple primitive types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfTypes { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for OneOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_with_null.rs.golden b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_with_null.rs.golden index 0022a079c..0b133073b 100644 --- a/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_with_null.rs.golden +++ b/tests/golden/rust/rust-reqwest/comprehensive-schemas/src/models/one_of_with_null.rs.golden @@ -4,12 +4,59 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of string, null, and schema reference -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfWithNull { String(String), BasicObject(super::BasicObject), } + +impl<'de> serde::Deserialize<'de> for OneOfWithNull { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfWithNull")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_0(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} diff --git a/tests/golden/rust/rust-reqwest/enum-repr/src/models/mixed_enum.rs.golden b/tests/golden/rust/rust-reqwest/enum-repr/src/models/mixed_enum.rs.golden index fda4acb9f..6ba6f55c7 100644 --- a/tests/golden/rust/rust-reqwest/enum-repr/src/models/mixed_enum.rs.golden +++ b/tests/golden/rust/rust-reqwest/enum-repr/src/models/mixed_enum.rs.golden @@ -4,13 +4,13 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Mixed enum with unit variants (SimpleA, SimpleB) and tuple variants (VariantA(VariantA), VariantB(VariantB)) /// /// Serializes as externally tagged: `{"SimpleA": null}`, `{"VariantA": {"field1": "...", "field2": 123}}`, /// `{"SimpleB": null}`, `{"VariantB": {"field3": true, "field4": 1.23}}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum MixedEnum { Variant0(String), @@ -18,3 +18,103 @@ pub enum MixedEnum { Variant2(String), MixedEnumMember4(super::MixedEnumMember4), } + +impl<'de> serde::Deserialize<'de> for MixedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(3); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MixedEnumMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + Some(3) => serde_json::from_value::(candidate).map(Self::MixedEnumMember4).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MixedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleA") +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantA").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleB") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantB").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_8(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/enum-repr/src/models/untagged_enum.rs.golden b/tests/golden/rust/rust-reqwest/enum-repr/src/models/untagged_enum.rs.golden index c343d3697..ae57e3671 100644 --- a/tests/golden/rust/rust-reqwest/enum-repr/src/models/untagged_enum.rs.golden +++ b/tests/golden/rust/rust-reqwest/enum-repr/src/models/untagged_enum.rs.golden @@ -4,15 +4,77 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Untagged enum /// /// No tag is used, variants are distinguished by their structure: /// `{"field1": "...", "field2": 123}` or `{"field3": true, "field4": 1.23}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum UntaggedEnum { VariantA(super::VariantA), VariantB(super::VariantB), } + +impl<'de> serde::Deserialize<'de> for UntaggedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::VariantA).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::VariantB).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union UntaggedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-complex-union/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-complex-union/src/models/foo.rs.golden index 3161f5330..552765d6b 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-complex-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-complex-union/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Complex Union Type Test — 1.0.0 // Test fixture for complex union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/foo.rs.golden index b90626b8b..c51d61780 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/foo.rs.golden @@ -4,12 +4,88 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Qux(super::Qux), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_4(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/qux.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/qux.rs.golden index 25664e6de..733e088ca 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/qux.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-nested-union/src/models/qux.rs.golden @@ -4,11 +4,69 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Qux { Bar(super::Bar), Baz(super::Baz), } + +impl<'de> serde::Deserialize<'de> for Qux { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Qux")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-union-mixed/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-union-mixed/src/models/foo.rs.golden index dad6c77c7..66f7443a1 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-union-mixed/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-union-mixed/src/models/foo.rs.golden @@ -4,12 +4,60 @@ // Union Type with Mixed Members Test — 1.0.0 // Test fixture for union types with both interfaces and primitives -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-union-with-any/src/models/config.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-union-with-any/src/models/config.rs.golden index 4126397a6..ece07e1d4 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-union-with-any/src/models/config.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-union-with-any/src/models/config.rs.golden @@ -4,12 +4,42 @@ // Union Type with Any Test — 1.0.0 // Test fixture for union types that include the any type -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Config { String(String), Number(f64), Variant2(serde_json::Value), } + +impl<'de> serde::Deserialize<'de> for Config { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Config")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-union-with-inline-objects/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-union-with-inline-objects/src/models/foo.rs.golden index 630ac5283..bd92cb532 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-union-with-inline-objects/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-union-with-inline-objects/src/models/foo.rs.golden @@ -4,13 +4,108 @@ // Union Type with Inline Objects Test — 1.0.0 // Test fixture for union types (oneOf) with inline object schemas that should generate named interfaces -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union type with inline object schemas -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { FooMember1(super::FooMember1), FooMember2(super::FooMember2), FooMember3(super::FooMember3), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FooMember1).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FooMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::FooMember3).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bar").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("baz").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("qux").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("quux").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("corge").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("grault").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("garply").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-union-with-interfaces/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-union-with-interfaces/src/models/foo.rs.golden index f49bad848..127fd1d4c 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-union-with-interfaces/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-union-with-interfaces/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Union Type with Interfaces Test — 1.0.0 // Test fixture for oneOf/anyOf union types with interface members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-reqwest/type-aliases-union-with-primitives/src/models/foo.rs.golden b/tests/golden/rust/rust-reqwest/type-aliases-union-with-primitives/src/models/foo.rs.golden index fc723f323..3a977057a 100644 --- a/tests/golden/rust/rust-reqwest/type-aliases-union-with-primitives/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-reqwest/type-aliases-union-with-primitives/src/models/foo.rs.golden @@ -4,12 +4,46 @@ // Union Type with Primitives Test — 1.0.0 // Test fixture for union types with primitive members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/union-first-match/Cargo.toml.golden new file mode 100644 index 000000000..fe3628f5a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-first-match" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-reqwest/union-first-match/README.md.golden b/tests/golden/rust/rust-reqwest/union-first-match/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/apis/selection.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/apis/selection.rs.golden new file mode 100644 index 000000000..236d70a23 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: reqwest::header::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/alpha.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..a629e03df --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous.rs.golden @@ -0,0 +1,78 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/beta.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..b840312d2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/binary_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_one.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_two.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_choice.rs.golden new file mode 100644 index 000000000..5c3ecf5ec --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_choice.rs.golden @@ -0,0 +1,126 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_one.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_two.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..a221aea9d --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_one.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_two.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/included_null.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_choice.rs.golden new file mode 100644 index 000000000..6b6af2c6e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_choice.rs.golden @@ -0,0 +1,133 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_one.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_two.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_choice.rs.golden new file mode 100644 index 000000000..9bfbfc5d2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_choice.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_one.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_outer.rs.golden new file mode 100644 index 000000000..6669192b8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_outer.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_two.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..d4d082c01 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..2b79fe48a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/nullable_fields.rs.golden @@ -0,0 +1,147 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..0569fef62 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/opaque_first.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_branch.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_map.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_nested.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_nested.rs.golden new file mode 100644 index 000000000..cba893366 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_nested.rs.golden @@ -0,0 +1,88 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree.rs.golden new file mode 100644 index 000000000..4662f2f73 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..7cefd61f5 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..d39ae673a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,101 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..ee1cfae8a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/scalar_choice.rs.golden @@ -0,0 +1,161 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_8(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/models/tree.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/models/tree.rs.golden new file mode 100644 index 000000000..878d21316 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/models/tree.rs.golden @@ -0,0 +1,151 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate, memo) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/auth.rs.golden new file mode 100644 index 000000000..55894dcd1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/auth.rs.golden @@ -0,0 +1,188 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/client.rs.golden new file mode 100644 index 000000000..a14befbbd --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/client.rs.golden @@ -0,0 +1,72 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/error.rs.golden new file mode 100644 index 000000000..87c70ec6f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/error.rs.golden @@ -0,0 +1,228 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &reqwest::header::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&reqwest::header::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-first-match/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/union-raw/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/union-raw/Cargo.toml.golden new file mode 100644 index 000000000..fb3bd2d8a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-raw" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-reqwest/union-raw/README.md.golden b/tests/golden/rust/rust-reqwest/union-raw/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/apis/selection.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/apis/selection.rs.golden new file mode 100644 index 000000000..236d70a23 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: reqwest::header::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/alpha.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..f3605f51f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/beta.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..e03496534 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/binary_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_one.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_two.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/container_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ef4eaf077 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/container_one.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/container_two.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..26e0713b3 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_one.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_two.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/included_null.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/large_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_choice.rs.golden new file mode 100644 index 000000000..f140cdf4a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/large_one.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/large_two.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/map_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_choice.rs.golden new file mode 100644 index 000000000..a33365975 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/map_one.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_outer.rs.golden new file mode 100644 index 000000000..b6c35b0c0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/map_two.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..27f3859c0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..65f8d4382 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/nullable_fields.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..67baf789e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/opaque_first.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/open_branch.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/open_map.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/open_nested.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_nested.rs.golden new file mode 100644 index 000000000..e4aec32e9 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_nested.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree.rs.golden new file mode 100644 index 000000000..95b617a5e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..94079e0ac --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..f83d98409 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..288e1f70e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/scalar_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/models/tree.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/models/tree.rs.golden new file mode 100644 index 000000000..bc126a306 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/models/tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/auth.rs.golden new file mode 100644 index 000000000..55894dcd1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/auth.rs.golden @@ -0,0 +1,188 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/client.rs.golden new file mode 100644 index 000000000..a14befbbd --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/client.rs.golden @@ -0,0 +1,72 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/error.rs.golden new file mode 100644 index 000000000..87c70ec6f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/error.rs.golden @@ -0,0 +1,228 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &reqwest::header::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&reqwest::header::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-reqwest/union-raw/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-raw/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/union-selection/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/union-selection/Cargo.toml.golden new file mode 100644 index 000000000..2480fd529 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "union-selection-contract" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-reqwest/union-selection/README.md.golden b/tests/golden/rust/rust-reqwest/union-selection/README.md.golden new file mode 100644 index 000000000..681418c90 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/README.md.golden @@ -0,0 +1,7 @@ +# Union selection contract + +Generated Rust 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/rust/rust-reqwest/union-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..e69b0985f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/apis/mod.rs.golden @@ -0,0 +1,5 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/lib.rs.golden new file mode 100644 index 000000000..bea884b24 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/choice.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/choice.rs.golden new file mode 100644 index 000000000..932f64635 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Choice { + NumericOne(super::NumericOne), + NumericTwo(super::NumericTwo), +} + +impl<'de> serde::Deserialize<'de> for Choice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::NumericOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NumericTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Choice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/client_tool.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/client_tool.rs.golden new file mode 100644 index 000000000..3643ac933 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/client_tool.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientTool { + pub input_schema: std::collections::HashMap, + pub name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/envelope.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/envelope.rs.golden new file mode 100644 index 000000000..401e7e986 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/envelope.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Envelope { + pub choice: super::Choice, + pub choice_list: Vec, + pub choice_map: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..9f7247500 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/mod.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +mod choice; +pub use choice::*; +mod client_tool; +pub use client_tool::*; +mod envelope; +pub use envelope::*; +mod numeric_one; +pub use numeric_one::*; +mod numeric_two; +pub use numeric_two::*; +mod request; +pub use request::*; +mod required_choice; +pub use required_choice::*; +mod required_one; +pub use required_one::*; +mod required_two; +pub use required_two::*; +mod search_tool; +pub use search_tool::*; +mod tool; +pub use tool::*; diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_one.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_one.rs.golden new file mode 100644 index 000000000..9ebb0f3d4 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_two.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_two.rs.golden new file mode 100644 index 000000000..96f26a90f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/numeric_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericTwo { + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/request.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/request.rs.golden new file mode 100644 index 000000000..10498f58d --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/request.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Request { + pub tools: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/required_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_choice.rs.golden new file mode 100644 index 000000000..ac6ac5fb2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum RequiredChoice { + RequiredOne(super::RequiredOne), + RequiredTwo(super::RequiredTwo), +} + +impl<'de> serde::Deserialize<'de> for RequiredChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::RequiredOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::RequiredTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RequiredChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("first_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("second_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/required_one.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_one.rs.golden new file mode 100644 index 000000000..42d46bdb7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredOne { + pub first_value: String, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/required_two.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_two.rs.golden new file mode 100644 index 000000000..f364d79ee --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/required_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredTwo { + pub revision: i64, + pub second_value: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/search_tool.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/search_tool.rs.golden new file mode 100644 index 000000000..de1df9010 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/search_tool.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchTool { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub max_uses: Option, + pub name: String, + #[serde(rename = "type")] + pub r#type: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/models/tool.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/models/tool.rs.golden new file mode 100644 index 000000000..57924d7e8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/models/tool.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Tool { + ClientTool(super::ClientTool), + SearchTool(super::SearchTool), +} + +impl<'de> serde::Deserialize<'de> for Tool { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClientTool).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::SearchTool).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tool")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("input_schema").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("max_uses").is_some_and(|item | !__nexus_matches_4(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search_20250305") +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..f38e8be86 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/auth.rs.golden @@ -0,0 +1,188 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..ad9c8fadc --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/client.rs.golden @@ -0,0 +1,72 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..dd351e9e8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/error.rs.golden @@ -0,0 +1,105 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &reqwest::header::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} diff --git a/tests/golden/rust/rust-reqwest/union-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..d24426638 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-selection/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/union-strict/Cargo.toml.golden b/tests/golden/rust/rust-reqwest/union-strict/Cargo.toml.golden new file mode 100644 index 000000000..91bbda0fe --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-strict" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +reqwest = { version = "0.12", features = ["json", "multipart"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-reqwest/union-strict/README.md.golden b/tests/golden/rust/rust-reqwest/union-strict/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/apis/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/apis/selection.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/apis/selection.rs.golden new file mode 100644 index 000000000..236d70a23 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub async fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.request(reqwest::Method::GET, &path).await?; + let resp = self.client.send(req).await?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.bytes().await.map_err(Error::Network)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(&body_bytes).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(&body_bytes).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: reqwest::header::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/lib.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/alpha.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..0f6de428f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous.rs.golden @@ -0,0 +1,84 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_2(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/beta.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..f52475aab --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/binary_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_one.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_two.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/container_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ec10517d3 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_choice.rs.golden @@ -0,0 +1,132 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/container_one.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/container_two.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..8c89d4236 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_one.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_two.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/included_null.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/large_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_choice.rs.golden new file mode 100644 index 000000000..7cc03e122 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_choice.rs.golden @@ -0,0 +1,139 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/large_one.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/large_two.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/map_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_choice.rs.golden new file mode 100644 index 000000000..ca809ee6a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_choice.rs.golden @@ -0,0 +1,100 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/map_one.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_outer.rs.golden new file mode 100644 index 000000000..4ef317b79 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_outer.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/map_two.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..49efce185 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..a9feb9d8a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/nullable_fields.rs.golden @@ -0,0 +1,153 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..86edb5f4f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/opaque_first.rs.golden @@ -0,0 +1,67 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpaqueFirst: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/open_branch.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/open_map.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/open_nested.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_nested.rs.golden new file mode 100644 index 000000000..1624f60c1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_nested.rs.golden @@ -0,0 +1,91 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenNested: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree.rs.golden new file mode 100644 index 000000000..683b0a00e --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenTree: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..f046c904b --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..3b6110e96 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union RecursiveMapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..c8b6dac14 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/scalar_choice.rs.golden @@ -0,0 +1,170 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(1); + } + if __nexus_matches_8(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/models/tree.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/models/tree.rs.golden new file mode 100644 index 000000000..9a8533288 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/models/tree.rs.golden @@ -0,0 +1,157 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/runtime/auth.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/auth.rs.golden new file mode 100644 index 000000000..55894dcd1 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/auth.rs.golden @@ -0,0 +1,188 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::{HeaderMap, HeaderValue}; + +use crate::runtime::error::Error; + +/// Trait for authenticating HTTP requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Apply authentication to the given headers. + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + let value = HeaderValue::from_str(&format!("Bearer {}", token)) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid auth header: {e}"), + })?; + headers.insert(reqwest::header::AUTHORIZATION, value); + Ok(()) + } +} + +/// API key authentication via header. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn authenticate(&self, headers: &mut HeaderMap) -> Result<(), Error> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + let name = reqwest::header::HeaderName::from_bytes(self.header_name.as_bytes()) + .map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header name: {e}"), + })?; + let value = HeaderValue::from_str(&key).map_err(|e| Error::Api { + status: reqwest::StatusCode::INTERNAL_SERVER_ERROR, + body: format!("invalid header value: {e}"), + })?; + headers.insert(name, value); + Ok(()) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/runtime/client.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/client.rs.golden new file mode 100644 index 000000000..a14befbbd --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/client.rs.golden @@ -0,0 +1,72 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +use reqwest::header::HeaderMap; + +use crate::runtime::auth::Authenticator; +use crate::runtime::error::Error; + +/// HTTP client wrapper around reqwest. +#[derive(Debug, Clone)] +pub struct Client { + inner: reqwest::Client, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: reqwest::Client::new(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a new client with a custom reqwest::Client. + pub fn with_client(inner: reqwest::Client, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Set the authenticator for this client. + pub fn with_auth(mut self, auth: impl Authenticator + 'static) -> Self { + self.authenticator = Some(Arc::new(auth)); + self + } + + /// Build a request with authentication applied. + pub async fn request( + &self, + method: reqwest::Method, + path: &str, + ) -> Result { + let url = format!("{}{}", self.base_url, path); + let mut builder = self.inner.request(method, &url); + + if let Some(auth) = &self.authenticator { + let mut headers = HeaderMap::new(); + auth.authenticate(&mut headers)?; + builder = builder.headers(headers); + } + + Ok(builder) + } + + /// Send a request and return the response. + pub async fn send( + &self, + request: reqwest::RequestBuilder, + ) -> Result { + let response = request.send().await.map_err(Error::Network)?; + Ok(response) + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/runtime/error.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/error.rs.golden new file mode 100644 index 000000000..87c70ec6f --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/error.rs.golden @@ -0,0 +1,228 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +/// Errors that can occur during API calls. +#[derive(Debug)] +pub enum Error { + /// Network-level error from reqwest. + Network(reqwest::Error), + /// API returned a non-success status code. + Api { + status: reqwest::StatusCode, + body: String, + }, + /// Failed to deserialize the response body. + Deserialize(serde_json::Error), + /// Failed to serialize the request body as XML. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Network(e) => write!(f, "network error: {e}"), + Error::Api { status, body } => write!(f, "API error {status}: {body}"), + Error::Deserialize(e) => write!(f, "deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Network(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Api { .. } => None, + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Network(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &reqwest::header::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: reqwest::header::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&reqwest::header::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-reqwest/union-strict/src/runtime/mod.rs.golden b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-reqwest/union-strict/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/feed_source.rs.golden b/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/feed_source.rs.golden index 0cb0e3c98..e3babc3ec 100644 --- a/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/feed_source.rs.golden +++ b/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/feed_source.rs.golden @@ -25,13 +25,13 @@ pub enum FeedSource { impl utoipa::PartialSchema for FeedSource { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for FeedSource { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("FeedSource") diff --git a/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/payment_method.rs.golden b/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/payment_method.rs.golden index 15a5b804e..fa33cf9e4 100644 --- a/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/payment_method.rs.golden +++ b/tests/golden/rust/rust-reqwest/utoipa-mixed/src/models/payment_method.rs.golden @@ -4,25 +4,78 @@ // Utoipa Mixed Types Test — 1.0.0 // Test fixture covering all schema kinds with utoipa enabled -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// A payment method (card or bank transfer) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum PaymentMethod { CardPayment(super::CardPayment), BankTransfer(super::BankTransfer), } +impl<'de> serde::Deserialize<'de> for PaymentMethod { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::CardPayment).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BankTransfer).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union PaymentMethod")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("card_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("cvv").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("expiry").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("account_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("routing_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for PaymentMethod { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for PaymentMethod { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("PaymentMethod") diff --git a/tests/golden/rust/rust-reqwest/utoipa-untagged-union/src/models/event.rs.golden b/tests/golden/rust/rust-reqwest/utoipa-untagged-union/src/models/event.rs.golden index c934331fa..266096b04 100644 --- a/tests/golden/rust/rust-reqwest/utoipa-untagged-union/src/models/event.rs.golden +++ b/tests/golden/rust/rust-reqwest/utoipa-untagged-union/src/models/event.rs.golden @@ -4,10 +4,10 @@ // Utoipa Untagged Union Test — 1.0.0 // Test fixture for untagged union manual utoipa impl generation -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// An event payload (click, purchase, or page view) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Event { ClickEvent(super::ClickEvent), @@ -15,16 +15,97 @@ pub enum Event { PageViewEvent(super::PageViewEvent), } +impl<'de> serde::Deserialize<'de> for Event { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClickEvent).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::PurchaseEvent).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::PageViewEvent).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Event")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("element_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("x").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("y").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("amount").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("currency").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("item_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("referrer").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for Event { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for Event { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("Event") diff --git a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/any_of_types.rs.golden b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/any_of_types.rs.golden index 2289e3e8d..e69d0db37 100644 --- a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/any_of_types.rs.golden +++ b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/any_of_types.rs.golden @@ -4,12 +4,38 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Any of multiple types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum AnyOfTypes { String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for AnyOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union AnyOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_objects.rs.golden b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_objects.rs.golden index 698d2f540..b2913d01c 100644 --- a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_objects.rs.golden +++ b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_objects.rs.golden @@ -4,12 +4,98 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of object types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfObjects { BasicObject(super::BasicObject), NestedObject(super::NestedObject), } + +impl<'de> serde::Deserialize<'de> for OneOfObjects { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NestedObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfObjects")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("metadata").is_some_and(|item | !__nexus_matches_5(item)) { + return false; + } + if object.get("user").is_some_and(|item | !__nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("created").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if object.get("name").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_types.rs.golden b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_types.rs.golden index 966337bf0..e4a7ba0ba 100644 --- a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_types.rs.golden +++ b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_types.rs.golden @@ -4,13 +4,47 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of multiple primitive types -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfTypes { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for OneOfTypes { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfTypes")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_with_null.rs.golden b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_with_null.rs.golden index 0022a079c..0b133073b 100644 --- a/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_with_null.rs.golden +++ b/tests/golden/rust/rust-ureq/comprehensive-schemas/src/models/one_of_with_null.rs.golden @@ -4,12 +4,59 @@ // Comprehensive Schema Types Example — 1.0.0 // Comprehensive test for all OpenAPI v3.1.2 schema types -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union of string, null, and schema reference -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum OneOfWithNull { String(String), BasicObject(super::BasicObject), } + +impl<'de> serde::Deserialize<'de> for OneOfWithNull { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BasicObject).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OneOfWithNull")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("active").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("id").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_0(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} diff --git a/tests/golden/rust/rust-ureq/enum-repr/src/models/mixed_enum.rs.golden b/tests/golden/rust/rust-ureq/enum-repr/src/models/mixed_enum.rs.golden index fda4acb9f..6ba6f55c7 100644 --- a/tests/golden/rust/rust-ureq/enum-repr/src/models/mixed_enum.rs.golden +++ b/tests/golden/rust/rust-ureq/enum-repr/src/models/mixed_enum.rs.golden @@ -4,13 +4,13 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Mixed enum with unit variants (SimpleA, SimpleB) and tuple variants (VariantA(VariantA), VariantB(VariantB)) /// /// Serializes as externally tagged: `{"SimpleA": null}`, `{"VariantA": {"field1": "...", "field2": 123}}`, /// `{"SimpleB": null}`, `{"VariantB": {"field3": true, "field4": 1.23}}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum MixedEnum { Variant0(String), @@ -18,3 +18,103 @@ pub enum MixedEnum { Variant2(String), MixedEnumMember4(super::MixedEnumMember4), } + +impl<'de> serde::Deserialize<'de> for MixedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(3); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MixedEnumMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + Some(3) => serde_json::from_value::(candidate).map(Self::MixedEnumMember4).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MixedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleA") +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantA").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("SimpleB") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("VariantB").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_8(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/enum-repr/src/models/untagged_enum.rs.golden b/tests/golden/rust/rust-ureq/enum-repr/src/models/untagged_enum.rs.golden index c343d3697..ae57e3671 100644 --- a/tests/golden/rust/rust-ureq/enum-repr/src/models/untagged_enum.rs.golden +++ b/tests/golden/rust/rust-ureq/enum-repr/src/models/untagged_enum.rs.golden @@ -4,15 +4,77 @@ // Enum Representation API — 1.0.0 // This API demonstrates all 4 kinds of enum representation types: Externally Tagged, Internally Tagged, Adjacently Tagged, and Untagged -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Untagged enum /// /// No tag is used, variants are distinguished by their structure: /// `{"field1": "...", "field2": 123}` or `{"field3": true, "field4": 1.23}` -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum UntaggedEnum { VariantA(super::VariantA), VariantB(super::VariantB), } + +impl<'de> serde::Deserialize<'de> for UntaggedEnum { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::VariantA).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::VariantB).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union UntaggedEnum")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field1").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("field2").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("field3").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("field4").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-complex-union/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-complex-union/src/models/foo.rs.golden index 3161f5330..552765d6b 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-complex-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-complex-union/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Complex Union Type Test — 1.0.0 // Test fixture for complex union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/foo.rs.golden index b90626b8b..c51d61780 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/foo.rs.golden @@ -4,12 +4,88 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Qux(super::Qux), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_4(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/qux.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/qux.rs.golden index 25664e6de..733e088ca 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/qux.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-nested-union/src/models/qux.rs.golden @@ -4,11 +4,69 @@ // Nested Union Type Test — 1.0.0 // Test fixture for union types that reference other union types -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Qux { Bar(super::Bar), Baz(super::Baz), } + +impl<'de> serde::Deserialize<'de> for Qux { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Qux")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-union-mixed/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-union-mixed/src/models/foo.rs.golden index dad6c77c7..66f7443a1 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-union-mixed/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-union-mixed/src/models/foo.rs.golden @@ -4,12 +4,60 @@ // Union Type with Mixed Members Test — 1.0.0 // Test fixture for union types with both interfaces and primitives -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), String(String), Number(f64), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-union-with-any/src/models/config.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-union-with-any/src/models/config.rs.golden index 4126397a6..ece07e1d4 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-union-with-any/src/models/config.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-union-with-any/src/models/config.rs.golden @@ -4,12 +4,42 @@ // Union Type with Any Test — 1.0.0 // Test fixture for union types that include the any type -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Config { String(String), Number(f64), Variant2(serde_json::Value), } + +impl<'de> serde::Deserialize<'de> for Config { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Variant2).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Config")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-union-with-inline-objects/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-union-with-inline-objects/src/models/foo.rs.golden index 630ac5283..bd92cb532 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-union-with-inline-objects/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-union-with-inline-objects/src/models/foo.rs.golden @@ -4,13 +4,108 @@ // Union Type with Inline Objects Test — 1.0.0 // Test fixture for union types (oneOf) with inline object schemas that should generate named interfaces -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// Union type with inline object schemas -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { FooMember1(super::FooMember1), FooMember2(super::FooMember2), FooMember3(super::FooMember3), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FooMember1).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FooMember2).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::FooMember3).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bar").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("baz").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("qux").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("quux").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("corge").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("grault").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("garply").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-union-with-interfaces/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-union-with-interfaces/src/models/foo.rs.golden index f49bad848..127fd1d4c 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-union-with-interfaces/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-union-with-interfaces/src/models/foo.rs.golden @@ -4,12 +4,98 @@ // Union Type with Interfaces Test — 1.0.0 // Test fixture for oneOf/anyOf union types with interface members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { Bar(super::Bar), Baz(super::Baz), Qux(super::Qux), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Bar).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Baz).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Qux).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("bucket").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("endpoint").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("bar") +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("path").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("baz") +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("namespace").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("qux") +} diff --git a/tests/golden/rust/rust-ureq/type-aliases-union-with-primitives/src/models/foo.rs.golden b/tests/golden/rust/rust-ureq/type-aliases-union-with-primitives/src/models/foo.rs.golden index fc723f323..3a977057a 100644 --- a/tests/golden/rust/rust-ureq/type-aliases-union-with-primitives/src/models/foo.rs.golden +++ b/tests/golden/rust/rust-ureq/type-aliases-union-with-primitives/src/models/foo.rs.golden @@ -4,12 +4,46 @@ // Union Type with Primitives Test — 1.0.0 // Test fixture for union types with primitive members -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Foo { String(String), Number(f64), Boolean(bool), } + +impl<'de> serde::Deserialize<'de> for Foo { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Foo")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/Cargo.toml.golden b/tests/golden/rust/rust-ureq/union-first-match/Cargo.toml.golden new file mode 100644 index 000000000..6fad69972 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-first-match" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-ureq/union-first-match/README.md.golden b/tests/golden/rust/rust-ureq/union-first-match/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/apis/selection.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/apis/selection.rs.golden new file mode 100644 index 000000000..67afd1c2d --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path); + let resp = req.call().map_err(Error::Http)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.into_body().read_to_vec().map_err(Error::Http)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: ureq::http::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/lib.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/alpha.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..a629e03df --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous.rs.golden @@ -0,0 +1,78 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/beta.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..b840312d2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/binary_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_one.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_two.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/container_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_choice.rs.golden new file mode 100644 index 000000000..5c3ecf5ec --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_choice.rs.golden @@ -0,0 +1,126 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/container_one.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/container_two.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..a221aea9d --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_one.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_two.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/included_null.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/large_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_choice.rs.golden new file mode 100644 index 000000000..6b6af2c6e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_choice.rs.golden @@ -0,0 +1,133 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/large_one.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/large_two.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/map_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_choice.rs.golden new file mode 100644 index 000000000..9bfbfc5d2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_choice.rs.golden @@ -0,0 +1,94 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/map_one.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_outer.rs.golden new file mode 100644 index 000000000..6669192b8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_outer.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/map_two.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..d4d082c01 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_choice.rs.golden @@ -0,0 +1,256 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_14(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..2b79fe48a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/nullable_fields.rs.golden @@ -0,0 +1,147 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..0569fef62 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/opaque_first.rs.golden @@ -0,0 +1,64 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/open_branch.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/open_map.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/open_nested.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_nested.rs.golden new file mode 100644 index 000000000..cba893366 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_nested.rs.golden @@ -0,0 +1,88 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree.rs.golden new file mode 100644 index 000000000..4662f2f73 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..7cefd61f5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,54 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_1(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..d39ae673a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,101 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..ee1cfae8a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/scalar_choice.rs.golden @@ -0,0 +1,161 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_8(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/models/tree.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/models/tree.rs.golden new file mode 100644 index 000000000..878d21316 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/models/tree.rs.golden @@ -0,0 +1,151 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate, memo) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate, memo) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/auth.rs.golden new file mode 100644 index 000000000..019d52720 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/auth.rs.golden @@ -0,0 +1,168 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/client.rs.golden new file mode 100644 index 000000000..131f73117 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/client.rs.golden @@ -0,0 +1,93 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req.config().http_status_as_error(false).build() + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/error.rs.golden new file mode 100644 index 000000000..05b6c96a8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/error.rs.golden @@ -0,0 +1,227 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &ureq::http::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&ureq::http::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-ureq/union-first-match/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-first-match/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/union-raw/Cargo.toml.golden b/tests/golden/rust/rust-ureq/union-raw/Cargo.toml.golden new file mode 100644 index 000000000..0c7380356 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-raw" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-ureq/union-raw/README.md.golden b/tests/golden/rust/rust-ureq/union-raw/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-ureq/union-raw/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-ureq/union-raw/src/apis/selection.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/apis/selection.rs.golden new file mode 100644 index 000000000..67afd1c2d --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path); + let resp = req.call().map_err(Error::Http)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.into_body().read_to_vec().map_err(Error::Http)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: ureq::http::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/lib.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/alpha.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..f3605f51f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/beta.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..e03496534 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/binary_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/branch_one.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/branch_two.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/container_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ef4eaf077 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/container_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/container_one.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/container_two.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..26e0713b3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/finite_one.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/finite_two.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/included_null.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/large_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/large_choice.rs.golden new file mode 100644 index 000000000..f140cdf4a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/large_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/large_one.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/large_two.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/map_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/map_choice.rs.golden new file mode 100644 index 000000000..a33365975 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/map_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/map_one.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/map_outer.rs.golden new file mode 100644 index 000000000..b6c35b0c0 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/map_two.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..27f3859c0 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..65f8d4382 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/nullable_fields.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..67baf789e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/opaque_first.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/open_branch.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/open_map.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/open_nested.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/open_nested.rs.golden new file mode 100644 index 000000000..e4aec32e9 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/open_nested.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree.rs.golden new file mode 100644 index 000000000..95b617a5e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..94079e0ac --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..f83d98409 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..288e1f70e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/scalar_choice.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/models/tree.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/models/tree.rs.golden new file mode 100644 index 000000000..bc126a306 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/models/tree.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + Raw(serde_json::Value), +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/runtime/auth.rs.golden new file mode 100644 index 000000000..019d52720 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/runtime/auth.rs.golden @@ -0,0 +1,168 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/runtime/client.rs.golden new file mode 100644 index 000000000..131f73117 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/runtime/client.rs.golden @@ -0,0 +1,93 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req.config().http_status_as_error(false).build() + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/runtime/error.rs.golden new file mode 100644 index 000000000..05b6c96a8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/runtime/error.rs.golden @@ -0,0 +1,227 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &ureq::http::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&ureq::http::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-ureq/union-raw/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/union-raw/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-raw/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/union-selection/Cargo.toml.golden b/tests/golden/rust/rust-ureq/union-selection/Cargo.toml.golden new file mode 100644 index 000000000..a91f8f2d6 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/Cargo.toml.golden @@ -0,0 +1,12 @@ +[package] +name = "union-selection-contract" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" diff --git a/tests/golden/rust/rust-ureq/union-selection/README.md.golden b/tests/golden/rust/rust-ureq/union-selection/README.md.golden new file mode 100644 index 000000000..681418c90 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/README.md.golden @@ -0,0 +1,7 @@ +# Union selection contract + +Generated Rust 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/rust/rust-ureq/union-selection/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/apis/mod.rs.golden new file mode 100644 index 000000000..e69b0985f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/apis/mod.rs.golden @@ -0,0 +1,5 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + diff --git a/tests/golden/rust/rust-ureq/union-selection/src/lib.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/lib.rs.golden new file mode 100644 index 000000000..bea884b24 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/choice.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/choice.rs.golden new file mode 100644 index 000000000..932f64635 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Choice { + NumericOne(super::NumericOne), + NumericTwo(super::NumericTwo), +} + +impl<'de> serde::Deserialize<'de> for Choice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::NumericOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::NumericTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Choice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/client_tool.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/client_tool.rs.golden new file mode 100644 index 000000000..3643ac933 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/client_tool.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClientTool { + pub input_schema: std::collections::HashMap, + pub name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/envelope.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/envelope.rs.golden new file mode 100644 index 000000000..401e7e986 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/envelope.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Envelope { + pub choice: super::Choice, + pub choice_list: Vec, + pub choice_map: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/mod.rs.golden new file mode 100644 index 000000000..9f7247500 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/mod.rs.golden @@ -0,0 +1,27 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +mod choice; +pub use choice::*; +mod client_tool; +pub use client_tool::*; +mod envelope; +pub use envelope::*; +mod numeric_one; +pub use numeric_one::*; +mod numeric_two; +pub use numeric_two::*; +mod request; +pub use request::*; +mod required_choice; +pub use required_choice::*; +mod required_one; +pub use required_one::*; +mod required_two; +pub use required_two::*; +mod search_tool; +pub use search_tool::*; +mod tool; +pub use tool::*; diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_one.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_one.rs.golden new file mode 100644 index 000000000..9ebb0f3d4 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_two.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_two.rs.golden new file mode 100644 index 000000000..96f26a90f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/numeric_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NumericTwo { + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/request.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/request.rs.golden new file mode 100644 index 000000000..10498f58d --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/request.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Request { + pub tools: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/required_choice.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/required_choice.rs.golden new file mode 100644 index 000000000..ac6ac5fb2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/required_choice.rs.golden @@ -0,0 +1,103 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum RequiredChoice { + RequiredOne(super::RequiredOne), + RequiredTwo(super::RequiredTwo), +} + +impl<'de> serde::Deserialize<'de> for RequiredChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::RequiredOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::RequiredTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RequiredChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("first_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_4(candidate) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("second_value").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_3(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/required_one.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/required_one.rs.golden new file mode 100644 index 000000000..42d46bdb7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/required_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredOne { + pub first_value: String, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/required_two.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/required_two.rs.golden new file mode 100644 index 000000000..f364d79ee --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/required_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RequiredTwo { + pub revision: i64, + pub second_value: String, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/search_tool.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/search_tool.rs.golden new file mode 100644 index 000000000..de1df9010 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/search_tool.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchTool { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub max_uses: Option, + pub name: String, + #[serde(rename = "type")] + pub r#type: String, +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/models/tool.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/models/tool.rs.golden new file mode 100644 index 000000000..57924d7e8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/models/tool.rs.golden @@ -0,0 +1,82 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum Tool { + ClientTool(super::ClientTool), + SearchTool(super::SearchTool), +} + +impl<'de> serde::Deserialize<'de> for Tool { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClientTool).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::SearchTool).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tool")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("input_schema").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("max_uses").is_some_and(|item | !__nexus_matches_4(item)) { + return false; + } + if!object.get("name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if!object.get("type").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search") +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.as_str() == Some("web_search_20250305") +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/runtime/auth.rs.golden new file mode 100644 index 000000000..8831bdad3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/runtime/auth.rs.golden @@ -0,0 +1,168 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/runtime/client.rs.golden new file mode 100644 index 000000000..4a748de02 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/runtime/client.rs.golden @@ -0,0 +1,93 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req.config().http_status_as_error(false).build() + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/runtime/error.rs.golden new file mode 100644 index 000000000..c69ea01b4 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/runtime/error.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &ureq::http::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} diff --git a/tests/golden/rust/rust-ureq/union-selection/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/union-selection/src/runtime/mod.rs.golden new file mode 100644 index 000000000..d24426638 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-selection/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Union selection contract — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/union-strict/Cargo.toml.golden b/tests/golden/rust/rust-ureq/union-strict/Cargo.toml.golden new file mode 100644 index 000000000..f668c64d5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/Cargo.toml.golden @@ -0,0 +1,13 @@ +[package] +name = "rust-union-strict" +version = "0.1.0" +edition = "2024" +description = "Generated Rust SDK." + +[dependencies] +ureq = { version = "3", features = ["json"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde-xml-rs = "0.8.2" +serde_repr = "0.1" +utoipa = "5" diff --git a/tests/golden/rust/rust-ureq/union-strict/README.md.golden b/tests/golden/rust/rust-ureq/union-strict/README.md.golden new file mode 100644 index 000000000..341df1c44 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/README.md.golden @@ -0,0 +1,7 @@ +# Rust union policies + +Generated Rust SDK. + +Version: `1.0.0` + +Generated by [openapi-nexus](https://github.com/rust-codegen-group/openapi-nexus) for `rust-union-policies`. diff --git a/tests/golden/rust/rust-ureq/union-strict/src/apis/mod.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/apis/mod.rs.golden new file mode 100644 index 000000000..947a22787 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/apis/mod.rs.golden @@ -0,0 +1,7 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod selection; +pub use selection::*; diff --git a/tests/golden/rust/rust-ureq/union-strict/src/apis/selection.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/apis/selection.rs.golden new file mode 100644 index 000000000..67afd1c2d --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/apis/selection.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use crate::runtime::client::Client; +use crate::runtime::error::{ApiCallError, ApiError, Error}; + +/// API operations under the "Selection" tag. +pub struct SelectionApi<'a> { + client: &'a Client, +} + +impl<'a> SelectionApi<'a> { + /// Create a new `SelectionApi` bound to the given client. + pub fn new(client: &'a Client) -> Self { + Self { + client, + } + } + + /// GET /choice + pub fn get_choice( + &self, + ) -> Result { + let path = "/choice".to_string(); + let req = self.client.get(&path); + let resp = req.call().map_err(Error::Http)?; + let status_code = resp.status().as_u16(); + let response_headers = resp.headers().clone(); + let body_bytes = resp.into_body().read_to_vec().map_err(Error::Http)?; + let mut result = GetChoiceResponse { status_code, headers: response_headers.clone(), data: None }; + match status_code { + 200 => { + result.data = Some(serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize)?); + } + _ => {} + } + if !(200..300).contains(&status_code) { + match status_code { + 400 => { + let body = serde_json::from_slice(body_bytes.as_slice()).map_err(Error::Deserialize); + return Err(GetChoiceError::BadRequest(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + _ => { + let body = Ok::, Error>(body_bytes.to_vec()); + return Err(GetChoiceError::Unexpected(ApiError::new(status_code, response_headers.clone(), body_bytes.to_vec(), body))); + } + } + } + Ok(result) + } +} + +/// Response from `get_choice`. +#[derive(Debug)] +pub struct GetChoiceResponse { + pub status_code: u16, + pub headers: ureq::http::HeaderMap, + pub data: Option, +} + +/// Error from `get_choice`. +#[derive(Debug)] +pub enum GetChoiceError { + BadRequest(ApiError), + Unexpected(ApiError>), + Transport(Error), +} + +impl From for GetChoiceError { + fn from(error: Error) -> Self { + Self::Transport(error) + } +} + +impl From for ApiCallError { + fn from(error: GetChoiceError) -> Self { + match error { + GetChoiceError::BadRequest(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Unexpected(error) => Self::from_api_error("getChoice", error), + GetChoiceError::Transport(error) => Self::from_runtime_error("getChoice", error), + } + } +} + +impl std::fmt::Display for GetChoiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::BadRequest(err) => write!(f, "HTTP error {}", err.status_code()), + Self::Unexpected(err) => write!(f, "unexpected HTTP error {}", err.status_code()), + Self::Transport(err) => std::fmt::Display::fmt(err, f), + } + } +} + +impl std::error::Error for GetChoiceError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Transport(err) => Some(err), + _ => None, + } + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/lib.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/lib.rs.golden new file mode 100644 index 000000000..78b85215f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/lib.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +#![allow(clippy::all)] + +pub mod apis; +pub mod models; +pub mod runtime; diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/alpha.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/alpha.rs.golden new file mode 100644 index 000000000..051acfcc8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/alpha.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Alpha { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous.rs.golden new file mode 100644 index 000000000..0f6de428f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous.rs.golden @@ -0,0 +1,84 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Ambiguous { + Alpha(super::Alpha), + Beta(super::Beta), +} + +impl<'de> serde::Deserialize<'de> for Ambiguous { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_2(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Ambiguous: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Beta).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Ambiguous")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for Ambiguous { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Ambiguous { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Ambiguous") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous_alias.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous_alias.rs.golden new file mode 100644 index 000000000..2d1dcaa96 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/ambiguous_alias.rs.golden @@ -0,0 +1,10 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +#[serde(transparent)] +pub struct AmbiguousAlias(pub super::Ambiguous); diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/beta.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/beta.rs.golden new file mode 100644 index 000000000..478bc5cd7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/beta.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct Beta { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/binary_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/binary_choice.rs.golden new file mode 100644 index 000000000..f52475aab --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/binary_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum BinaryChoice { + Binary(Vec), + String(String), +} + +impl<'de> serde::Deserialize<'de> for BinaryChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union BinaryChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::>(candidate).map(Self::Binary).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union BinaryChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | {items.iter().all(| item | item.as_u64().is_some_and(| number | number <= 255))}) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for BinaryChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item( as utoipa::PartialSchema >::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for BinaryChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("BinaryChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/branch_one.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/branch_one.rs.golden new file mode 100644 index 000000000..57a75ee73 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/branch_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchOne { + pub children: Vec, + pub revision: i64, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/branch_two.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/branch_two.rs.golden new file mode 100644 index 000000000..53626116c --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/branch_two.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct BranchTwo { + pub children: Vec, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/container_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/container_choice.rs.golden new file mode 100644 index 000000000..ec10517d3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/container_choice.rs.golden @@ -0,0 +1,132 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ContainerChoice { + ContainerOne(super::ContainerOne), + ContainerTwo(super::ContainerTwo), +} + +impl<'de> serde::Deserialize<'de> for ContainerChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ContainerChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ContainerOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::ContainerTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ContainerChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_8(item))) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for ContainerChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ContainerChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ContainerChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/container_one.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/container_one.rs.golden new file mode 100644 index 000000000..289118d70 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/container_one.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/container_two.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/container_two.rs.golden new file mode 100644 index 000000000..809064931 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/container_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ContainerTwo { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/excluded_null.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/excluded_null.rs.golden new file mode 100644 index 000000000..b774de6a2 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/excluded_null.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct ExcludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/finite_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_choice.rs.golden new file mode 100644 index 000000000..8c89d4236 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum FiniteChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for FiniteChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union FiniteChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union FiniteChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for FiniteChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for FiniteChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("FiniteChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/finite_one.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_one.rs.golden new file mode 100644 index 000000000..6bca5f120 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_one.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteOne { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub first_value: Option, + pub flags: std::collections::HashMap, + pub revision: i64, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/finite_two.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_two.rs.golden new file mode 100644 index 000000000..34a61c5fa --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/finite_two.rs.golden @@ -0,0 +1,15 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct FiniteTwo { + pub flags: std::collections::HashMap, + pub revision: i64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, + pub values: Vec, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/included_null.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/included_null.rs.golden new file mode 100644 index 000000000..5f58481ab --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/included_null.rs.golden @@ -0,0 +1,14 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct IncludedNull { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/large_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/large_choice.rs.golden new file mode 100644 index 000000000..7cc03e122 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/large_choice.rs.golden @@ -0,0 +1,139 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum LargeChoice { + LargeOne(super::LargeOne), + LargeTwo(super::LargeTwo), +} + +impl<'de> serde::Deserialize<'de> for LargeChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union LargeChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::LargeOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::LargeTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union LargeChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740992_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740992_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if __nexus_matches_7(candidate) { + return true; + } + if __nexus_matches_8(candidate) { + return true; + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(9007199254740993_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 9007199254740993_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_u64() == Some(18446744073709551615_u64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= 0.0 && number < u64::MAX as f64 + &&number as u64 == 18446744073709551615_u64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for LargeChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for LargeChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("LargeChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/large_one.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/large_one.rs.golden new file mode 100644 index 000000000..fbfe2d7f7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/large_one.rs.golden @@ -0,0 +1,11 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeOne { + pub revision: u64, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/large_two.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/large_two.rs.golden new file mode 100644 index 000000000..89ea2d462 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/large_two.rs.golden @@ -0,0 +1,13 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct LargeTwo { + pub revision: u64, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub second_value: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/map_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/map_choice.rs.golden new file mode 100644 index 000000000..ca809ee6a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/map_choice.rs.golden @@ -0,0 +1,100 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapChoice { + MapOne(super::MapOne), + MapTwo(super::MapTwo), +} + +impl<'de> serde::Deserialize<'de> for MapChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::MapOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::MapTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_1(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_5(item))) +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +impl utoipa::PartialSchema for MapChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/map_one.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/map_one.rs.golden new file mode 100644 index 000000000..8d05ecaad --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/map_one.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapOne { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/map_outer.rs.golden new file mode 100644 index 000000000..4ef317b79 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/map_outer.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum MapOuter { + Variant0(serde_json::Value), + OpenMap(super::OpenMap), +} + +impl<'de> serde::Deserialize<'de> for MapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union MapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_4(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union MapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_3(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for MapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for MapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("MapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/map_two.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/map_two.rs.golden new file mode 100644 index 000000000..50bf1779e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/map_two.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct MapTwo { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/mod.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/mod.rs.golden new file mode 100644 index 000000000..053ff07d3 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/mod.rs.golden @@ -0,0 +1,77 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +mod alpha; +pub use alpha::*; +mod ambiguous; +pub use ambiguous::*; +mod ambiguous_alias; +pub use ambiguous_alias::*; +mod beta; +pub use beta::*; +mod binary_choice; +pub use binary_choice::*; +mod branch_one; +pub use branch_one::*; +mod branch_two; +pub use branch_two::*; +mod container_choice; +pub use container_choice::*; +mod container_one; +pub use container_one::*; +mod container_two; +pub use container_two::*; +mod excluded_null; +pub use excluded_null::*; +mod finite_choice; +pub use finite_choice::*; +mod finite_one; +pub use finite_one::*; +mod finite_two; +pub use finite_two::*; +mod included_null; +pub use included_null::*; +mod large_choice; +pub use large_choice::*; +mod large_one; +pub use large_one::*; +mod large_two; +pub use large_two::*; +mod map_choice; +pub use map_choice::*; +mod map_one; +pub use map_one::*; +mod map_outer; +pub use map_outer::*; +mod map_two; +pub use map_two::*; +mod nullable_choice; +pub use nullable_choice::*; +mod nullable_envelope; +pub use nullable_envelope::*; +mod nullable_fields; +pub use nullable_fields::*; +mod opaque_first; +pub use opaque_first::*; +mod open_branch; +pub use open_branch::*; +mod open_map; +pub use open_map::*; +mod open_nested; +pub use open_nested::*; +mod open_tree; +pub use open_tree::*; +mod open_tree_map; +pub use open_tree_map::*; +mod optional_opaque; +pub use optional_opaque::*; +mod optional_opaque_choice; +pub use optional_opaque_choice::*; +mod recursive_map_outer; +pub use recursive_map_outer::*; +mod scalar_choice; +pub use scalar_choice::*; +mod tree; +pub use tree::*; diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_choice.rs.golden new file mode 100644 index 000000000..49efce185 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_choice.rs.golden @@ -0,0 +1,262 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableChoice { + FiniteOne(super::FiniteOne), + FiniteTwo(super::FiniteTwo), +} + +impl<'de> serde::Deserialize<'de> for NullableChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_14(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::FiniteOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::FiniteTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("flags").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_6(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_3(item))) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(false) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_8(candidate) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_10(item))) +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_12(candidate) { + return false; + } + true +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + if __nexus_matches_8(candidate) { + return true; + } + if __nexus_matches_13(candidate) { + return true; + } + false +} + +fn __nexus_matches_13(candidate: &serde_json::Value) -> bool { + let expected = 1.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_14(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("flags").is_some_and(|item | __nexus_matches_15(item)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_18(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("values").is_some_and(|item | __nexus_matches_20(item)) { + return false; + } + true +} + +fn __nexus_matches_15(candidate: &serde_json::Value) -> bool { + candidate.as_object().is_some_and(| items | items.values().all(| item | __nexus_matches_16(item))) +} + +fn __nexus_matches_16(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_17(candidate) { + return false; + } + true +} + +fn __nexus_matches_17(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_18(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_7(candidate) { + return false; + } + if !__nexus_matches_19(candidate) { + return false; + } + true +} + +fn __nexus_matches_19(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_20(candidate: &serde_json::Value) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_21(item))) +} + +fn __nexus_matches_21(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_11(candidate) { + return false; + } + if !__nexus_matches_22(candidate) { + return false; + } + true +} + +fn __nexus_matches_22(candidate: &serde_json::Value) -> bool { + if __nexus_matches_19(candidate) { + return true; + } + if __nexus_matches_23(candidate) { + return true; + } + false +} + +fn __nexus_matches_23(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +impl utoipa::PartialSchema for NullableChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_envelope.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_envelope.rs.golden new file mode 100644 index 000000000..9eaf29dd5 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_envelope.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct NullableEnvelope { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub choice: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_fields.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_fields.rs.golden new file mode 100644 index 000000000..a9feb9d8a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/nullable_fields.rs.golden @@ -0,0 +1,153 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum NullableFields { + ExcludedNull(super::ExcludedNull), + IncludedNull(super::IncludedNull), +} + +impl<'de> serde::Deserialize<'de> for NullableFields { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_6(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union NullableFields: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ExcludedNull).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::IncludedNull).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union NullableFields")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_3(candidate) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_4(candidate) { + return false; + } + if !__nexus_matches_5(candidate) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("revision").is_some_and(|item | __nexus_matches_7(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_12(item)) { + return false; + } + true +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_8(candidate) { + return false; + } + if !__nexus_matches_9(candidate) { + return false; + } + true +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_null() || __nexus_matches_4(candidate) +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if __nexus_matches_10(candidate) { + return true; + } + if __nexus_matches_11(candidate) { + return true; + } + false +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + candidate.is_null() +} + +fn __nexus_matches_12(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for NullableFields { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for NullableFields { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NullableFields") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/opaque_first.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/opaque_first.rs.golden new file mode 100644 index 000000000..86edb5f4f --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/opaque_first.rs.golden @@ -0,0 +1,67 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpaqueFirst { + Variant0(serde_json::Value), + Alpha(super::Alpha), +} + +impl<'de> serde::Deserialize<'de> for OpaqueFirst { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpaqueFirst: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Alpha).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpaqueFirst")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpaqueFirst { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpaqueFirst { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpaqueFirst") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/open_branch.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/open_branch.rs.golden new file mode 100644 index 000000000..995dba73a --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/open_branch.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenBranch { + pub children: Vec, + pub wire_name: String, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/open_map.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/open_map.rs.golden new file mode 100644 index 000000000..0b599f367 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/open_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/open_nested.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/open_nested.rs.golden new file mode 100644 index 000000000..1624f60c1 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/open_nested.rs.golden @@ -0,0 +1,91 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenNested { + AmbiguousAlias(super::AmbiguousAlias), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenNested { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenNested: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::AmbiguousAlias).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenNested")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_3(candidate) { + return true; + } + false +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("first_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("second_value").is_some_and(|item | !__nexus_matches_2(item)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +impl utoipa::PartialSchema for OpenNested { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenNested { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenNested") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree.rs.golden new file mode 100644 index 000000000..683b0a00e --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree.rs.golden @@ -0,0 +1,85 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OpenTree { + OpenBranch(super::OpenBranch), + Variant1(serde_json::Value), +} + +impl<'de> serde::Deserialize<'de> for OpenTree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OpenTree: multiple concrete members match")); + } + selected = Some(0); + } + if selected.is_none() && true { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OpenBranch).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Variant1).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OpenTree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_3(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OpenTree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OpenTree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OpenTree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree_map.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree_map.rs.golden new file mode 100644 index 000000000..30c312dce --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/open_tree_map.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OpenTreeMap { + #[serde(flatten)] + pub additional_properties: std::collections::HashMap, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque.rs.golden new file mode 100644 index 000000000..4b27002d7 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque.rs.golden @@ -0,0 +1,12 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, utoipa::ToSchema)] +pub struct OptionalOpaque { + #[serde(skip_serializing_if = "Option::is_none", default)] + pub metadata: Option, +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque_choice.rs.golden new file mode 100644 index 000000000..f046c904b --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/optional_opaque_choice.rs.golden @@ -0,0 +1,60 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum OptionalOpaqueChoice { + OptionalOpaque(super::OptionalOpaque), + String(String), +} + +impl<'de> serde::Deserialize<'de> for OptionalOpaqueChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_1(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union OptionalOpaqueChoice: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::OptionalOpaque).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::String).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union OptionalOpaqueChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +impl utoipa::PartialSchema for OptionalOpaqueChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for OptionalOpaqueChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("OptionalOpaqueChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/recursive_map_outer.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/recursive_map_outer.rs.golden new file mode 100644 index 000000000..3b6110e96 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/recursive_map_outer.rs.golden @@ -0,0 +1,104 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum RecursiveMapOuter { + Variant0(serde_json::Value), + OpenTreeMap(super::OpenTreeMap), +} + +impl<'de> serde::Deserialize<'de> for RecursiveMapOuter { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union RecursiveMapOuter: multiple concrete members match")); + } + selected = Some(1); + } + if selected.is_none() && true { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_6(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Variant0).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::OpenTreeMap).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union RecursiveMapOuter")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + candidate.as_object().is_some_and(| items | items.values().any(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_3(item, memo)) { + return false; + } + if!object.get("wire_name").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + true +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_4(item, memo))) +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (4, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {true})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_object() +} + +impl utoipa::PartialSchema for RecursiveMapOuter { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for RecursiveMapOuter { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("RecursiveMapOuter") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/scalar_choice.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/scalar_choice.rs.golden new file mode 100644 index 000000000..c8b6dac14 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/scalar_choice.rs.golden @@ -0,0 +1,170 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum ScalarChoice { + Integer(i64), + Number(f64), + Boolean(bool), +} + +impl<'de> serde::Deserialize<'de> for ScalarChoice { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if __nexus_matches_0(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(1); + } + if __nexus_matches_8(&candidate) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union ScalarChoice: multiple concrete members match")); + } + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::Integer).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::Number).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::Boolean).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union ScalarChoice")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_1(candidate) { + return false; + } + if !__nexus_matches_2(candidate) { + return false; + } + if !__nexus_matches_3(candidate) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + if __nexus_matches_1(candidate) { + return true; + } + if __nexus_matches_2(candidate) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_5(candidate) { + return false; + } + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + let expected = 2.5_f64; + if candidate.is_f64() { + return candidate.as_f64() == Some(expected); + } + if expected.fract() != 0.0 { + return false; + } + if expected >= i64::MIN as f64 && expected < -(i64::MIN as f64) { + return candidate.as_i64() == Some(expected as i64); + } + if expected >= 0.0 && expected < u64::MAX as f64 { + return candidate.as_u64() == Some(expected as u64); + } + false +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if __nexus_matches_5(candidate) { + return true; + } + if __nexus_matches_6(candidate) { + return true; + } + false +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_9(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + if !__nexus_matches_11(candidate) { + return false; + } + true +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + candidate.is_boolean() +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + candidate.as_bool() == Some(true) +} + +fn __nexus_matches_11(candidate: &serde_json::Value) -> bool { + if __nexus_matches_9(candidate) { + return true; + } + if __nexus_matches_10(candidate) { + return true; + } + false +} + +impl utoipa::PartialSchema for ScalarChoice { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for ScalarChoice { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("ScalarChoice") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/models/tree.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/models/tree.rs.golden new file mode 100644 index 000000000..9a8533288 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/models/tree.rs.golden @@ -0,0 +1,157 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use serde::Serialize; + +#[derive(Debug, Clone, Serialize, PartialEq)] +#[serde(untagged)] +pub enum Tree { + BranchOne(super::BranchOne), + BranchTwo(super::BranchTwo), +} + +impl<'de> serde::Deserialize<'de> for Tree { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let memo = &mut std::collections::HashMap::new(); + let mut selected = None; + if __nexus_matches_0(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(0); + } + if __nexus_matches_4(&candidate, memo) { + if selected.is_some() { + return Err(serde::de::Error::custom("ambiguous untagged union Tree: multiple concrete members match")); + } + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::BranchOne).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BranchTwo).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Tree")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_9(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + candidate.as_array().is_some_and(| items | items.iter().all(| item | __nexus_matches_2(item, memo))) +} + +fn __nexus_matches_2(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let key = (2, std::ptr::from_ref(candidate)); + if let Some(result) = memo.get(&key) { + return *result; + } + let result = (|| {__nexus_matches_3(candidate, memo)})(); + memo.insert(key, result); + result +} + +fn __nexus_matches_3(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + if __nexus_matches_0(candidate, memo) { + return true; + } + if __nexus_matches_4(candidate, memo) { + return true; + } + false +} + +fn __nexus_matches_4(candidate: &serde_json::Value, + memo: &mut std::collections::HashMap<(usize, *const serde_json::Value), bool>) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("children").is_some_and(|item | __nexus_matches_1(item, memo)) { + return false; + } + if!object.get("revision").is_some_and(|item | __nexus_matches_5(item)) { + return false; + } + if object.get("second_value").is_some_and(|item | !__nexus_matches_8(item)) { + return false; + } + true +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_7(candidate) { + return false; + } + true +} + +fn __nexus_matches_6(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_7(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(2_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 2_i64}) +} + +fn __nexus_matches_8(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_9(candidate: &serde_json::Value) -> bool { + if !__nexus_matches_6(candidate) { + return false; + } + if !__nexus_matches_10(candidate) { + return false; + } + true +} + +fn __nexus_matches_10(candidate: &serde_json::Value) -> bool { + if!candidate.is_f64() { + return candidate.as_i64() == Some(1_i64); + } + candidate.as_f64().is_some_and(| number | {number.fract() == 0.0 && number >= i64::MIN as f64 && number < -(i64::MIN as f64) + &&number as i64 == 1_i64}) +} + +impl utoipa::PartialSchema for Tree { + fn schema() -> utoipa::openapi::RefOr { + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() + } +} +impl utoipa::ToSchema for Tree { + fn name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("Tree") + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/runtime/auth.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/runtime/auth.rs.golden new file mode 100644 index 000000000..019d52720 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/runtime/auth.rs.golden @@ -0,0 +1,168 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::sync::Arc; + +/// Trait for authenticating requests. +pub trait Authenticator: Send + Sync + std::fmt::Debug { + /// Return header key-value pairs to apply to the request. + fn auth_headers(&self) -> Vec<(&str, String)>; +} + +/// Bearer token authentication. +/// +/// Use [`BearerAuth::new`] with a static token, or +/// [`BearerAuth::from_provider`] with a function that returns the +/// current token (evaluated on every request). +pub struct BearerAuth { + token: TokenSource, +} + +enum TokenSource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for TokenSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(t) => f.debug_tuple("Static").field(&t).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for TokenSource { + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for BearerAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BearerAuth").field("token", &self.token).finish() + } +} + +impl Clone for BearerAuth { + fn clone(&self) -> Self { + Self { + token: self.token.clone(), + } + } +} + +impl BearerAuth { + /// Create a new bearer token authenticator with a static token. + pub fn new(token: impl Into) -> Self { + Self { + token: TokenSource::Static(token.into()), + } + } + + /// Create a bearer token authenticator that evaluates the given + /// function on every request to obtain the current token. + pub fn from_provider(f: impl Fn() -> String + Send + Sync + 'static) -> Self { + Self { + token: TokenSource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for BearerAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let token = match &self.token { + TokenSource::Static(t) => t.clone(), + TokenSource::Dynamic(f) => f(), + }; + vec![("Authorization", format!("Bearer {}", token))] + } +} + +/// API key authentication. +/// +/// Use [`ApiKeyAuth::new`] with a static key, or +/// [`ApiKeyAuth::from_provider`] with a function that returns the +/// current key (evaluated on every request). +pub struct ApiKeyAuth { + header_name: String, + api_key: KeySource, +} + +enum KeySource { + Static(String), + Dynamic(Arc String + Send + Sync>), +} + +impl std::fmt::Debug for KeySource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Static(k) => f.debug_tuple("Static").field(&k).finish(), + Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + +impl Clone for KeySource { + fn clone(&self) -> Self { + match self { + Self::Static(k) => Self::Static(k.clone()), + Self::Dynamic(f) => Self::Dynamic(Arc::clone(f)), + } + } +} + +impl std::fmt::Debug for ApiKeyAuth { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ApiKeyAuth") + .field("header_name", &self.header_name) + .field("api_key", &self.api_key) + .finish() + } +} + +impl Clone for ApiKeyAuth { + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + api_key: self.api_key.clone(), + } + } +} + +impl ApiKeyAuth { + /// Create a new API key authenticator with a static key. + pub fn new(header_name: impl Into, api_key: impl Into) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Static(api_key.into()), + } + } + + /// Create an API key authenticator that evaluates the given + /// function on every request to obtain the current key. + pub fn from_provider( + header_name: impl Into, + f: impl Fn() -> String + Send + Sync + 'static, + ) -> Self { + Self { + header_name: header_name.into(), + api_key: KeySource::Dynamic(Arc::new(f)), + } + } +} + +impl Authenticator for ApiKeyAuth { + fn auth_headers(&self) -> Vec<(&str, String)> { + let key = match &self.api_key { + KeySource::Static(k) => k.clone(), + KeySource::Dynamic(f) => f(), + }; + vec![(&self.header_name, key)] + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/runtime/client.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/runtime/client.rs.golden new file mode 100644 index 000000000..131f73117 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/runtime/client.rs.golden @@ -0,0 +1,93 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; +use std::sync::Arc; + +use crate::runtime::auth::Authenticator; + +/// Synchronous HTTP client wrapping `ureq::Agent`. +pub struct Client { + inner: ureq::Agent, + base_url: String, + authenticator: Option>, +} + +impl Client { + /// Create a new client with the given base URL. + pub fn new(base_url: &str) -> Self { + Self { + inner: ureq::Agent::new_with_defaults(), + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Create a client with a custom `ureq::Agent`. + pub fn with_agent(inner: ureq::Agent, base_url: &str) -> Self { + Self { + inner, + base_url: base_url.trim_end_matches('/').to_string(), + authenticator: None, + } + } + + /// Attach an authenticator to the client. + pub fn with_auth(mut self, auth: Arc) -> Self { + self.authenticator = Some(auth); + self + } + + fn full_url(&self, path: &str) -> String { + format!("{}{}", self.base_url, path) + } + + fn apply_auth(&self, mut req: ureq::RequestBuilder) -> ureq::RequestBuilder { + if let Some(auth) = &self.authenticator { + for (key, value) in auth.auth_headers() { + req = req.header(key, &value); + } + } + req.config().http_status_as_error(false).build() + } + + /// Build a GET request. + pub fn get(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.get(&self.full_url(path))) + } + + /// Build a POST request. + pub fn post(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.post(&self.full_url(path))) + } + + /// Build a PUT request. + pub fn put(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.put(&self.full_url(path))) + } + + /// Build a DELETE request. + pub fn delete(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.delete(&self.full_url(path))) + } + + /// Build a PATCH request. + pub fn patch(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.patch(&self.full_url(path))) + } + + /// Build a HEAD request. + pub fn head(&self, path: &str) -> ureq::RequestBuilder { + self.apply_auth(self.inner.head(&self.full_url(path))) + } +} + +impl fmt::Debug for Client { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Client") + .field("base_url", &self.base_url) + .finish() + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/runtime/error.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/runtime/error.rs.golden new file mode 100644 index 000000000..05b6c96a8 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/runtime/error.rs.golden @@ -0,0 +1,227 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +use std::fmt; + +/// Error types for the generated SDK. +#[derive(Debug)] +pub enum Error { + /// HTTP transport error. + Http(ureq::Error), + /// I/O error while reading response body. + Io(std::io::Error), + /// JSON deserialization error. + Deserialize(serde_json::Error), + /// XML serialization error. + Xml(serde_xml_rs::Error), + /// Unsupported generated operation. + Unsupported(&'static str), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Http(e) => write!(f, "HTTP error: {e}"), + Error::Io(e) => write!(f, "IO error: {e}"), + Error::Deserialize(e) => write!(f, "Deserialization error: {e}"), + Error::Xml(e) => write!(f, "XML serialization error: {e}"), + Error::Unsupported(e) => write!(f, "Unsupported operation: {e}"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Http(e) => Some(e), + Error::Io(e) => Some(e), + Error::Deserialize(e) => Some(e), + Error::Xml(e) => Some(e), + Error::Unsupported(_) => None, + } + } +} + +impl From for Error { + fn from(e: ureq::Error) -> Self { + Error::Http(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Deserialize(e) + } +} + +impl From for Error { + fn from(e: serde_xml_rs::Error) -> Self { + Error::Xml(e) + } +} + +/// Typed HTTP error response payload. +#[derive(Debug)] +pub struct ApiError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, +} + +impl ApiError { + pub fn new( + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body: Result, + ) -> Self { + Self { + status_code, + headers, + raw_body, + body, + } + } + + pub fn status_code(&self) -> u16 { + self.status_code + } + + pub fn headers(&self) -> &ureq::http::HeaderMap { + &self.headers + } + + pub fn raw_body(&self) -> &[u8] { + &self.raw_body + } + + pub fn body(&self) -> Result<&T, &Error> { + self.body.as_ref() + } +} + +#[derive(Debug)] +struct ApiCallHttpError { + status_code: u16, + headers: ureq::http::HeaderMap, + raw_body: Vec, + body_error: Option, +} + +impl ApiCallHttpError { + fn from_api_error(error: ApiError) -> Self { + Self { + status_code: error.status_code, + headers: error.headers, + raw_body: error.raw_body, + body_error: error.body.err(), + } + } +} + +#[derive(Debug)] +enum ApiCallErrorKind { + Http(ApiCallHttpError), + Runtime(Error), +} + +#[derive(Debug)] +struct ApiCallErrorInner { + operation_id: &'static str, + kind: ApiCallErrorKind, +} + +impl ApiCallErrorInner { + fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Http(ApiCallHttpError::from_api_error(error)), + } + } + + fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + operation_id, + kind: ApiCallErrorKind::Runtime(error), + } + } +} + +/// Type-erased error from any generated API operation. +#[derive(Debug)] +pub struct ApiCallError { + inner: Box, +} + +impl ApiCallError { + pub(crate) fn from_api_error(operation_id: &'static str, error: ApiError) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_api_error(operation_id, error)), + } + } + + pub(crate) fn from_runtime_error(operation_id: &'static str, error: Error) -> Self { + Self { + inner: Box::new(ApiCallErrorInner::from_runtime_error(operation_id, error)), + } + } + + /// Operation identifier, generated from the HTTP method and path when OpenAPI omits one. + pub fn operation_id(&self) -> &'static str { + self.inner.operation_id + } + + /// HTTP response status, if the operation reached the server and received an error response. + pub fn status_code(&self) -> Option { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(error.status_code), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Native HTTP response headers, if an error response was received. + pub fn headers(&self) -> Option<&ureq::http::HeaderMap> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.headers), + ApiCallErrorKind::Runtime(_) => None, + } + } + + /// Raw HTTP response body, if an error response was received. + pub fn raw_body(&self) -> Option<&[u8]> { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => Some(&error.raw_body), + ApiCallErrorKind::Runtime(_) => None, + } + } +} + +impl std::fmt::Display for ApiCallError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.inner.kind { + ApiCallErrorKind::Http(error) => { + write!(f, "operation {} failed with HTTP status {}", self.inner.operation_id, error.status_code) + } + ApiCallErrorKind::Runtime(error) => { + write!(f, "operation {} failed: {error}", self.inner.operation_id) + } + } + } +} + +impl std::error::Error for ApiCallError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + if let ApiCallErrorKind::Http(error) = &self.inner.kind { + if let Some(error) = &error.body_error { + return Some(error); + } + } + if let ApiCallErrorKind::Runtime(error) = &self.inner.kind { + return Some(error); + } + None + } +} diff --git a/tests/golden/rust/rust-ureq/union-strict/src/runtime/mod.rs.golden b/tests/golden/rust/rust-ureq/union-strict/src/runtime/mod.rs.golden new file mode 100644 index 000000000..56d4f0fa0 --- /dev/null +++ b/tests/golden/rust/rust-ureq/union-strict/src/runtime/mod.rs.golden @@ -0,0 +1,8 @@ +// @generated +// Code generated by openapi-nexus. DO NOT EDIT. +// +// Rust union policies — 1.0.0 + +pub mod auth; +pub mod client; +pub mod error; diff --git a/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/feed_source.rs.golden b/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/feed_source.rs.golden index 0cb0e3c98..e3babc3ec 100644 --- a/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/feed_source.rs.golden +++ b/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/feed_source.rs.golden @@ -25,13 +25,13 @@ pub enum FeedSource { impl utoipa::PartialSchema for FeedSource { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for FeedSource { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("FeedSource") diff --git a/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/payment_method.rs.golden b/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/payment_method.rs.golden index 15a5b804e..fa33cf9e4 100644 --- a/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/payment_method.rs.golden +++ b/tests/golden/rust/rust-ureq/utoipa-mixed/src/models/payment_method.rs.golden @@ -4,25 +4,78 @@ // Utoipa Mixed Types Test — 1.0.0 // Test fixture covering all schema kinds with utoipa enabled -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// A payment method (card or bank transfer) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum PaymentMethod { CardPayment(super::CardPayment), BankTransfer(super::BankTransfer), } +impl<'de> serde::Deserialize<'de> for PaymentMethod { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_2(&candidate) { + selected = Some(1); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::CardPayment).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::BankTransfer).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union PaymentMethod")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("card_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("cvv").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("expiry").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("account_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("routing_number").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for PaymentMethod { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for PaymentMethod { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("PaymentMethod") diff --git a/tests/golden/rust/rust-ureq/utoipa-untagged-union/src/models/event.rs.golden b/tests/golden/rust/rust-ureq/utoipa-untagged-union/src/models/event.rs.golden index c934331fa..266096b04 100644 --- a/tests/golden/rust/rust-ureq/utoipa-untagged-union/src/models/event.rs.golden +++ b/tests/golden/rust/rust-ureq/utoipa-untagged-union/src/models/event.rs.golden @@ -4,10 +4,10 @@ // Utoipa Untagged Union Test — 1.0.0 // Test fixture for untagged union manual utoipa impl generation -use serde::{Deserialize, Serialize}; +use serde::Serialize; /// An event payload (click, purchase, or page view) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum Event { ClickEvent(super::ClickEvent), @@ -15,16 +15,97 @@ pub enum Event { PageViewEvent(super::PageViewEvent), } +impl<'de> serde::Deserialize<'de> for Event { + fn deserialize>(deserializer: D) -> Result { + let candidate = < serde_json::Value as serde::Deserialize >::deserialize(deserializer)?; + let mut selected = None; + if selected.is_none() && __nexus_matches_0(&candidate) { + selected = Some(0); + } + if selected.is_none() && __nexus_matches_3(&candidate) { + selected = Some(1); + } + if selected.is_none() && __nexus_matches_5(&candidate) { + selected = Some(2); + } + match selected { + Some(0) => serde_json::from_value::(candidate).map(Self::ClickEvent).map_err(serde::de::Error::custom), + Some(1) => serde_json::from_value::(candidate).map(Self::PurchaseEvent).map_err(serde::de::Error::custom), + Some(2) => serde_json::from_value::(candidate).map(Self::PageViewEvent).map_err(serde::de::Error::custom), + _ => Err(serde::de::Error::custom("value does not match any member of untagged union Event")), + } + } +} + +fn __nexus_matches_0(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("element_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("x").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + if!object.get("y").is_some_and(|item | __nexus_matches_2(item)) { + return false; + } + true +} + +fn __nexus_matches_1(candidate: &serde_json::Value) -> bool { + candidate.is_string() +} + +fn __nexus_matches_2(candidate: &serde_json::Value) -> bool { + candidate.is_i64() || candidate.is_u64() +} + +fn __nexus_matches_3(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if!object.get("amount").is_some_and(|item | __nexus_matches_4(item)) { + return false; + } + if!object.get("currency").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + if!object.get("item_id").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + +fn __nexus_matches_4(candidate: &serde_json::Value) -> bool { + candidate.is_number() +} + +fn __nexus_matches_5(candidate: &serde_json::Value) -> bool { + let object = match candidate.as_object() { + Some(object) => object, + None => return false, + }; + if object.get("referrer").is_some_and(|item | !__nexus_matches_1(item)) { + return false; + } + if!object.get("url").is_some_and(|item | __nexus_matches_1(item)) { + return false; + } + true +} + impl utoipa::PartialSchema for Event { fn schema() -> utoipa::openapi::RefOr { - utoipa::openapi::schema::Schema::OneOf(utoipa::openapi::schema::OneOfBuilder::new() - .item(::schema()) - .item(::schema()) - .item(::schema()) - .build()).into() + utoipa::openapi::schema::Schema::OneOf( + utoipa::openapi::schema::OneOfBuilder::new() + .item(::schema()).item(::schema()).item(::schema()) + + .build()).into() } } - impl utoipa::ToSchema for Event { fn name() -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed("Event") diff --git a/tests/golden_tests_rust_aioduct.rs b/tests/golden_tests_rust_aioduct.rs index cd7b22472..69dbc987a 100644 --- a/tests/golden_tests_rust_aioduct.rs +++ b/tests/golden_tests_rust_aioduct.rs @@ -247,3 +247,36 @@ fn test_utoipa_with_extra_derives_golden() { UPDATE_HINT, ); } + +#[test] +fn test_union_selection_golden() { + run_golden_test( + &RustAioductCodeGenerator::new(Default::default()), + golden_dir(), + "union-selection", + "valid/union-selection.yaml", + UPDATE_HINT, + ); +} + +#[test] +fn test_untagged_union_policies_golden() { + let configs: toml::value::Table = + toml::from_str(include_str!("generated-clients/rust/union-policies.toml")).unwrap(); + for (strategy, suffix) in [ + ("firstMatch", "first-match"), + ("strict", "strict"), + ("raw", "raw"), + ] { + let mut config = configs["rust-aioduct"].as_table().unwrap().clone(); + config.insert("untagged_union_strategy".into(), strategy.into()); + config.insert("crate_name".into(), format!("rust-union-{suffix}").into()); + run_golden_test( + &RustAioductCodeGenerator::new(config), + golden_dir(), + &format!("union-{suffix}"), + "valid/rust-union-policies.yaml", + UPDATE_HINT, + ); + } +} diff --git a/tests/golden_tests_rust_reqwest.rs b/tests/golden_tests_rust_reqwest.rs index a7b22aa3c..37331200d 100644 --- a/tests/golden_tests_rust_reqwest.rs +++ b/tests/golden_tests_rust_reqwest.rs @@ -247,3 +247,36 @@ fn test_utoipa_with_extra_derives_golden() { UPDATE_HINT, ); } + +#[test] +fn test_union_selection_golden() { + run_golden_test( + &RustReqwestCodeGenerator::new(Default::default()), + golden_dir(), + "union-selection", + "valid/union-selection.yaml", + UPDATE_HINT, + ); +} + +#[test] +fn test_untagged_union_policies_golden() { + let configs: toml::value::Table = + toml::from_str(include_str!("generated-clients/rust/union-policies.toml")).unwrap(); + for (strategy, suffix) in [ + ("firstMatch", "first-match"), + ("strict", "strict"), + ("raw", "raw"), + ] { + let mut config = configs["rust-reqwest"].as_table().unwrap().clone(); + config.insert("untagged_union_strategy".into(), strategy.into()); + config.insert("crate_name".into(), format!("rust-union-{suffix}").into()); + run_golden_test( + &RustReqwestCodeGenerator::new(config), + golden_dir(), + &format!("union-{suffix}"), + "valid/rust-union-policies.yaml", + UPDATE_HINT, + ); + } +} diff --git a/tests/golden_tests_rust_ureq.rs b/tests/golden_tests_rust_ureq.rs index 405683db1..85bfa173d 100644 --- a/tests/golden_tests_rust_ureq.rs +++ b/tests/golden_tests_rust_ureq.rs @@ -247,3 +247,36 @@ fn test_utoipa_with_extra_derives_golden() { UPDATE_HINT, ); } + +#[test] +fn test_union_selection_golden() { + run_golden_test( + &RustUreqCodeGenerator::new(Default::default()), + golden_dir(), + "union-selection", + "valid/union-selection.yaml", + UPDATE_HINT, + ); +} + +#[test] +fn test_untagged_union_policies_golden() { + let configs: toml::value::Table = + toml::from_str(include_str!("generated-clients/rust/union-policies.toml")).unwrap(); + for (strategy, suffix) in [ + ("firstMatch", "first-match"), + ("strict", "strict"), + ("raw", "raw"), + ] { + let mut config = configs["rust-ureq"].as_table().unwrap().clone(); + config.insert("untagged_union_strategy".into(), strategy.into()); + config.insert("crate_name".into(), format!("rust-union-{suffix}").into()); + run_golden_test( + &RustUreqCodeGenerator::new(config), + golden_dir(), + &format!("union-{suffix}"), + "valid/rust-union-policies.yaml", + UPDATE_HINT, + ); + } +} diff --git a/tests/ir_scalar_enums.rs b/tests/ir_scalar_enums.rs index b8c29d3c2..73dcbdeaf 100644 --- a/tests/ir_scalar_enums.rs +++ b/tests/ir_scalar_enums.rs @@ -167,7 +167,7 @@ components: } #[test] -fn scalar_enum_metadata_keeps_other_generators_on_the_legacy_representation() { +fn scalar_enum_metadata_changes_only_rust_union_dispatch_and_not_public_representations() { let source = r#"openapi: 3.1.0 info: {title: Scalar representations, version: '1'} paths: @@ -296,6 +296,20 @@ components: let actual = files(&decorated); let expected = files(&legacy); for (filename, expected) in &expected { + if generator.generator_type().language() == openapi_nexus::codegen::Language::Rust + && filename == "Models/choice.rs" + { + let actual = &actual[filename]; + assert_ne!(actual, expected, "Rust dispatch consumes finite facts"); + assert_eq!( + actual.split("\nimpl").next(), + expected.split("\nimpl").next(), + "typed variants and payloads do not change" + ); + assert!(actual.contains("Some(1_i64)")); + assert!(actual.contains("Some(true)")); + continue; + } assert_eq!( actual.get(filename), Some(expected), diff --git a/tests/rust_error_generation.rs b/tests/rust_error_generation.rs index 0fbdebd6d..1a393711f 100644 --- a/tests/rust_error_generation.rs +++ b/tests/rust_error_generation.rs @@ -5,15 +5,22 @@ use openapi_nexus::generators::rust::aioduct::RustAioductCodeGenerator; use openapi_nexus::generators::rust::reqwest::RustReqwestCodeGenerator; use openapi_nexus::generators::rust::ureq::RustUreqCodeGenerator; use openapi_nexus::generators::{ArtifactKind, GeneratorError}; -use openapi_nexus::ir::types::{IrPrimitive, IrResponse, IrSpec, IrTypeExpr}; +use openapi_nexus::ir::types::{ + IrPrimitive, IrResponse, IrSchema, IrSchemaKind, IrSpec, IrTaggedUnion, IrTaggedVariant, + IrTypeExpr, TaggingStyle, +}; use openapi_nexus::test_utils::read_fixture; use sigil_stitch::error::SigilStitchError; fn generators() -> [Box; 3] { + generators_with_config(toml::value::Table::new()) +} + +fn generators_with_config(config: toml::value::Table) -> [Box; 3] { [ - Box::new(RustReqwestCodeGenerator::new(toml::value::Table::new())), - Box::new(RustUreqCodeGenerator::new(toml::value::Table::new())), - Box::new(RustAioductCodeGenerator::new(toml::value::Table::new())), + Box::new(RustReqwestCodeGenerator::new(config.clone())), + Box::new(RustUreqCodeGenerator::new(config.clone())), + Box::new(RustAioductCodeGenerator::new(config)), ] } @@ -188,3 +195,52 @@ fn invalid_error_payload_returns_a_contextual_emission_error() { } } } + +#[test] +fn invalid_tagged_schema_returns_a_contextual_emission_error_instead_of_omitting_the_model() { + let mut ir = minimal_ir(); + ir.schemas.insert( + "TaggedChoice".into(), + IrSchema { + name: "TaggedChoice".into(), + description: None, + deprecated: false, + is_component: true, + kind: IrSchemaKind::TaggedUnion(IrTaggedUnion { + discriminator_field: "kind".into(), + tagging: TaggingStyle::External, + variants: vec![IrTaggedVariant { + discriminator_value: "payload".into(), + content_type: IrTypeExpr::Named(String::new()), + description: None, + additional_properties: None, + }], + }), + }, + ); + let config = toml::from_str("[utoipa]\nenabled = true").unwrap(); + for generator in generators_with_config(config) { + let backend = generator.generator_type(); + let error = generator + .generate(&ir) + .expect_err("invalid model must not be skipped"); + match error { + GeneratorError::Emission { + artifact, + name, + source, + } => { + assert!( + matches!(artifact, ArtifactKind::Model), + "{backend}: {artifact}" + ); + assert_eq!(name, "TaggedChoice", "{backend}"); + assert!( + matches!(*source, SigilStitchError::InvalidTypeName { .. }), + "{backend}: {source}" + ); + } + other => panic!("{backend}: expected a typed model emission error, got {other:?}"), + } + } +}