-
-
Notifications
You must be signed in to change notification settings - Fork 7.2k
feat(rust): implement true anyOf support with OR semantics #21915
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat(rust): implement true anyOf support with OR semantics #21915
Conversation
- anyOf now generates struct with optional fields instead of enum - Each anyOf option becomes an optional field prefixed with 'as_' - Added validate_any_of() method to ensure at least one field is set - Maintains semantic difference between anyOf (OR) and oneOf (XOR) - oneOf continues to use untagged enums (exactly one match) - anyOf uses struct with optional fields (one or more matches possible) BREAKING CHANGE: anyOf schemas now generate different code structure. Previously generated enums, now generates structs with optional fields to properly support OpenAPI anyOf semantics where multiple schemas can validate simultaneously.
- Added comprehensive test spec covering various oneOf/anyOf scenarios - Tests simple primitives, complex objects, and nested schemas - Tests oneOf with and without discriminator - Tests anyOf with overlapping properties - Added documentation explaining semantic differences - Demonstrates that oneOf picks first matching (untagged enum) - Demonstrates that anyOf allows multiple matches (struct with optional fields) The tests verify: - oneOf generates enums (XOR semantics) - anyOf generates structs with optional fields (OR semantics) - oneOf with discriminator generates tagged enums - anyOf validation ensures at least one field is set
- Added links to OpenAPI 3.1.0 spec for oneOf/anyOf definitions - Added links to JSON Schema documentation - Added comprehensive comparison of how different languages handle oneOf/anyOf - Documented how Java, TypeScript, Python, Go, and C# handle these constructs - Explained how each language deals with untagged union deserialization - Added comparison table showing implementation approaches Key findings: - Most languages handle oneOf/anyOf at runtime with custom deserializers - TypeScript uses native union types for both (no semantic difference) - Only Rust and Python truly differentiate anyOf (OR) from oneOf (XOR) - Java uses AbstractOpenApiSchema with TypeAdapters for both - All implementations try types in order for untagged unions
when it's ready, can you please pull my PR (branch) into yours to include tests for anyOf? ref: #21911 |
- Documented that NO generator refuses to generate for ambiguous schemas - Added detailed breakdown of each language's ambiguity handling strategy - Documented Swift's oneOfUnknownDefaultCase option for unmatched values - Explained Python's strict ValidationError approach - Documented Java's pragmatic 'first match wins' approach - Added common warnings and limitations from the codebase - Included best practices for avoiding ambiguity (discriminators, mutual exclusion, ordering) - Explained why generators choose fallbacks over refusing generation Key findings: - All generators have fallback strategies rather than refusing - Python has the strictest validation (ValidationError for multiple oneOf matches) - TypeScript is the most permissive (structural typing, no runtime validation) - Most use 'first match wins' for untagged unions - Pragmatism wins over strictness due to real-world API imperfections
- Added analysis of whether untagged enums violate OpenAPI/JSON Schema spec - Documented that discriminator is optional ('when used') not required - Clarified that oneOf MUST validate exactly one match per JSON Schema - Documented that 'first match wins' violates strict spec compliance - Added compliance table showing Python as only fully compliant implementation - Included example of what fully compliant Rust code would look like Key finding: Current implementations prioritize pragmatism over strict compliance. Most generators (including Rust) use 'first match wins' which technically violates the oneOf requirement that exactly one schema must match. The spec allows validation errors, and strictly speaking, generators should validate all schemas to ensure exactly one matches for oneOf.
- Added allOf as required composition (struct with all required fields) - Clarified anyOf as optional composition (struct with optional fields) - Confirmed oneOf as exclusive choice (enum) - Added comprehensive comparison table of all three keywords - Included correct Rust implementations for each - Documented the fundamental pattern: composition vs choice Key insight: - allOf and anyOf are BOTH compositional (merge schemas) - allOf requires ALL fields, anyOf allows optional combinations - oneOf is NOT compositional (exclusive choice) Most generators incorrectly treat anyOf like oneOf (choice) when it should be compositional like allOf but with optional fields.
my suggestion is to focus on just this change (fix anyOf) to start with |
…penapi-generator into refactoring/rust-true-anyof-support
…Of support - Merged PR OpenAPITools#21911 from wing328 which adds anyOf test samples - Regenerated samples with our new anyOf implementation - Models now correctly generate as structs with optional fields (OR semantics) - ModelIdentifier and AnotherAnyOfTest demonstrate proper anyOf behavior Co-Authored-By: William Cheng <[email protected]>
Documented the real-world implications of the anyOf fix by showing: - How the old generator would produce duplicate enum variants (broken Rust code) - Why wing328's test case perfectly demonstrates the problem - The practical difference between treating anyOf as choice vs composition These findings emerged from merging PR OpenAPITools#21911 and seeing firsthand how the old behavior would literally generate uncompilable code for certain anyOf schemas.
Created detailed documentation covering: - Current type mappings from OpenAPI to Rust - How oneOf, anyOf, and allOf are handled (or not) - Alternative approaches that were considered - Configuration options and their impact - Known limitations and rationale for decisions - Proposed changes in PR OpenAPITools#21915 for true anyOf support This documentation provides the context needed to understand both the current implementation and the proposed improvements.
Moved from docs/ to docs/generators/ where generator-specific documentation belongs, alongside rust.md, rust-server.md, etc.
Enhanced the type mapping documentation with: - Alternative composition approach for allOf using serde flatten - Explanation of how to avoid property name conflicts - Generated names for anonymous objects (Object$1, Object$2) - Trade-offs between flattening vs composition approaches This approach would maintain type safety while avoiding the complex property merging issues that arise with allOf schemas.
Added comprehensive comparison showing how different strongly-typed languages handle these OpenAPI constructs: - Java: Interface/inheritance approach, treats anyOf as oneOf - TypeScript: Union/intersection types (most natural fit) - Go: Explicit struct with pointers and custom unmarshaling - C#: Abstract classes and polymorphic serialization - Python: Union types with runtime validation - Swift: Enums with associated values (perfect for oneOf) Key findings: - No language correctly implements anyOf semantics (one or more) - Most treat anyOf identical to oneOf (exactly one) - TypeScript's type system is best suited for these constructs - Rust's proposed struct approach is reasonable given its constraints This comparison validates that the anyOf problem is industry-wide, not specific to Rust, and that our proposed solution is pragmatic.
all tests passed. @timvw is this PR ready for review? |
No... I hope to find some time to work on this coming weekend... |
FYI. There's another PR to add anyOf support to the rust-axum server generator: #21948. Please review when you've time to see if you like the implementation in that PR |
Summary
This PR implements true anyOf support for the Rust generator, properly differentiating it from oneOf semantics as defined in the OpenAPI specification.
Key Changes
OpenAPI Specification Compliance
Implementation Details
validate_any_of()
method to ensure at least one field is setTesting
Documentation
Breaking Changes
Related Issues
PR checklist
master