feat: union semantics - #134
Conversation
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core schema lowering and code emission semantics for oneOf/anyOf across multiple subsystems, so a final human review should confirm compatibility expectations despite strong test coverage.
Pull request overview
This PR implements new union semantics in oapi-codegen by changing how oneOf and anyOf schemas are lowered and emitted: oneOf becomes an exclusive (“exactly one match”) tag-free Rust enum with a custom Deserialize, while anyOf becomes a raw serde_json::Value wrapper that preserves the full JSON payload and provides typed “views” via as_<alt>() accessors (directionally enabled).
Changes:
- Switch
oneOfdeserialization from serde’s first-match#[serde(untagged)]behavior to “exactly one successful Rust payload decode”. - Represent
anyOfas a transparent raw-JSON wrapper withas_value()/into_value()plus typed accessors and directionalTryFrom<Value>/From<Value>construction. - Add/refresh documentation, fixtures, generated outputs, and tests (including coverage catalogue entries and warnings) to lock in the new behavior.
File summaries
| File | Description |
|---|---|
| docs/extensions.md | Updates extension docs to describe the new oneOf vs anyOf representations, naming rules, and accessor collision behavior. |
| docs/design.md | Replaces “first-match” union discussion with the new Rust-deserialization-based matching model and documents deliberate limits. |
| crates/oapi-codegen/src/ir.rs | Adds EnumKind::AnyOf and updates union IR docs to reflect exclusive matching for oneOf. |
| crates/oapi-codegen/src/lower/schema.rs | Differentiates oneOf vs anyOf at lowering time; adds anyOf accessor collision detection; adjusts discriminator mapping naming. |
| crates/oapi-codegen/src/lower/rename.rs | Extends rename rewriting to cover both Union and AnyOf variant payload types. |
| crates/oapi-codegen/src/lower/prune.rs | Ensures pruning considers references inside both Union and AnyOf variants. |
| crates/oapi-codegen/src/lower/direction.rs | Preserves Union vs AnyOf kind through request/response projection while projecting variant payload types. |
| crates/oapi-codegen/src/lower/recurse.rs | Adds rejection for non-consuming union/alias recursion and updates recursion boxing logic to include AnyOf. |
| crates/oapi-codegen/src/emit/mod.rs | Registers the new emit::union module. |
| crates/oapi-codegen/src/emit/models.rs | Routes union emission to the new dedicated emit::union helpers; exposes internal helpers to the module. |
| crates/oapi-codegen/src/emit/union.rs | New emitter for exclusive oneOf custom deserialization and anyOf raw-JSON wrapper + typed views. |
| crates/oapi-codegen/src/emit/usage.rs | Prevents anyOf raw wrappers from propagating foreign non-serde trait constraints to other models. |
| crates/oapi-codegen/src/coverage.rs | Updates schema keyword warnings to the new “Rust deserialization constraints affect match counts” message. |
| crates/oapi-codegen/tests/coverage.rs | Adds union-related catalogue entries, fixtures, and assertions; updates supported/unsupported classification for duplicate oneOf variant types. |
| crates/oapi-codegen/tests/generated.rs | Adds new unit tests covering the new oneOf/anyOf semantics, recursion rules, directional traits, and collision diagnostics. |
| crates/oapi-codegen/tests/fixtures/union_semantics.yaml | New fixture exercising exclusive oneOf, raw anyOf, ambiguity cases, discriminator naming effects, and recursion through arrays/properties. |
| crates/oapi-codegen/tests/fixtures/union_duplicate.yaml | Updates duplicate-type oneOf fixture narrative to match new exclusive-matching semantics. |
| crates/oapi-codegen/tests/fixtures/union_prelude_names.yaml | New fixture ensuring union models don’t shadow prelude-y names (TryFrom, Result) in generated APIs. |
| crates/oapi-codegen/tests/fixtures/server_union_traits.yaml | New server fixture validating directional serde trait behavior with foreign x-rust-type payloads. |
| crates/oapi-codegen/tests/fixtures/client_union_traits.yaml | New client fixture validating opposite-direction trait behavior for foreign payloads. |
| crates/oapi-codegen/tests/fixtures/recursive_schema.yaml | Adjusts recursive union fixture to ensure recursion consumes input via an object property. |
| crates/oapi-codegen/tests/fixtures/unsupported_union_accessor_collision.yaml | New fixture asserting accessor collision detection for anyOf (reserved method names). |
| crates/oapi-codegen/tests/generated/union_semantics.rs | New generated snapshot for the union semantics fixture (custom oneOf deserialize + anyOf wrapper). |
| crates/oapi-codegen/tests/generated/union_duplicate.rs | New generated snapshot showing duplicate-type oneOf now errors at runtime due to ambiguous matches. |
| crates/oapi-codegen/tests/generated/union_prelude_names.rs | New generated snapshot for prelude name collision coverage. |
| crates/oapi-codegen/tests/generated/server_union_traits.rs | New generated server snapshot validating directional serde derives and raw-wrapper behavior. |
| crates/oapi-codegen/tests/generated/client_union_traits.rs | New generated client snapshot validating directional serde derives and raw-wrapper behavior. |
| crates/oapi-codegen/tests/generated/anyof_untagged.rs | Updates anyOf snapshot from untagged enum to raw JSON wrapper representation. |
| crates/oapi-codegen/tests/generated/oneof_untagged.rs | Updates oneOf snapshot to use custom exclusive-match Deserialize implementation. |
| crates/oapi-codegen/tests/generated/oneof_discriminator.rs | Updates discriminator oneOf snapshot to use custom exclusive-match Deserialize implementation. |
| crates/oapi-codegen/tests/generated/oneof_variant_naming.rs | Updates multiple oneOf snapshots to use custom exclusive-match Deserialize implementation. |
| crates/oapi-codegen/tests/generated/type_name_collisions.rs | Updates a oneOf snapshot to use custom exclusive-match Deserialize implementation. |
| crates/oapi-codegen/tests/generated/recursive_schema.rs | Updates recursive union snapshot to the new recursion shape and deserialization semantics. |
| crates/oapi-codegen/tests/generated/combined_read_write_only.rs | Extends combined fixture snapshots with new union route and request/response projected union models. |
| crates/oapi-codegen/tests/generated/combined_x_rust_derive.rs | Extends combined fixture snapshot with new union models using the new semantics. |
| crates/oapi-codegen/tests/generated/package_combined_x_rust_derive/models.rs | Extends packaged snapshot with new union models using the new semantics. |
Review details
- Files reviewed: 24/38 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🎉 This PR is included in version 1.3.0-dev.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.3.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
No description provided.