Skip to content

feat: union semantics - #134

Merged
dotkas merged 3 commits into
developfrom
dotkas/oapi-union-semantics
Sep 8, 2026
Merged

dotkas merged 3 commits into
developfrom
dotkas/oapi-union-semantics

Conversation

@dotkas

@dotkas dotkas commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

No description provided.

@dotkas
dotkas marked this pull request as ready for review September 8, 2026 18:22
Copilot AI lite review requested due to automatic review settings September 8, 2026 18:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 oneOf deserialization from serde’s first-match #[serde(untagged)] behavior to “exactly one successful Rust payload decode”.
  • Represent anyOf as a transparent raw-JSON wrapper with as_value()/into_value() plus typed accessors and directional TryFrom<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.

Copilot AI review requested due to automatic review settings September 8, 2026 18:31
@dotkas
dotkas enabled auto-merge (squash) September 8, 2026 18:32
@dotkas
dotkas merged commit 20fb7f4 into develop Sep 8, 2026
7 of 8 checks passed
@dotkas
dotkas deleted the dotkas/oapi-union-semantics branch September 8, 2026 18:33
@alchemax-housekeeper

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.3.0-dev.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@alchemax-housekeeper

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.3.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants