Skip to content

Commit af9ae0a

Browse files
committed
docs(rust): add OpenAPI spec links and cross-language comparison
- 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
1 parent 46db574 commit af9ae0a

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

docs/rust-oneof-anyof-semantics.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ The Rust OpenAPI generator properly implements the semantic differences between
77
- **oneOf (XOR)**: Exactly one of the schemas must validate
88
- **anyOf (OR)**: One or more of the schemas must validate
99

10+
### OpenAPI Specification References
11+
12+
From the [OpenAPI 3.1.0 Specification](https://spec.openapis.org/oas/v3.1.0#schema-object):
13+
14+
- **[oneOf](https://spec.openapis.org/oas/v3.1.0#composition-and-inheritance-polymorphism)**: "Validates the value against exactly one of the subschemas"
15+
- **[anyOf](https://spec.openapis.org/oas/v3.1.0#composition-and-inheritance-polymorphism)**: "Validates the value against any (one or more) of the subschemas"
16+
17+
These keywords come from [JSON Schema](https://json-schema.org/understanding-json-schema/reference/combining.html) and maintain the same semantics.
18+
1019
## Implementation Details
1120

1221
### oneOf - Untagged Enums
@@ -162,6 +171,56 @@ pub enum ShapeOneOf {
162171
}
163172
```
164173

174+
## How Other Languages Handle oneOf/anyOf
175+
176+
### Java (with Gson)
177+
- **oneOf**: Uses `AbstractOpenApiSchema` base class with custom type adapters
178+
- **anyOf**: Similar to oneOf but allows multiple matches in validation
179+
- **Approach**: Runtime type checking with reflection, tries to deserialize into each type
180+
- **Untagged Issue**: Handled via custom `TypeAdapter` that attempts each type sequentially
181+
182+
### TypeScript
183+
- **oneOf**: Simple union types using `|` operator (e.g., `string | number | Person`)
184+
- **anyOf**: Same as oneOf - TypeScript union types
185+
- **Approach**: Type unions are natural in TypeScript, runtime validation depends on library
186+
- **Untagged Issue**: Not an issue - TypeScript's structural typing handles this naturally
187+
188+
### Python (Pydantic)
189+
- **oneOf**: Uses `Union` types with custom validation
190+
- **anyOf**: Separate class with `actual_instance` that validates against multiple schemas
191+
- **Approach**: Runtime validation with explicit checks for which schemas match
192+
- **Untagged Issue**: Custom deserializer tries each type and keeps track of matches
193+
194+
### Go
195+
- **oneOf**: Struct with pointer fields for each option, custom `UnmarshalJSON`
196+
- **anyOf**: Similar structure but allows multiple fields to be non-nil
197+
- **Approach**: All options as pointers, unmarshal attempts to populate each
198+
- **Untagged Issue**: Custom unmarshaler tries each type, oneOf ensures only one succeeds
199+
200+
### C#
201+
- **oneOf**: Uses inheritance with base class and custom JSON converters
202+
- **anyOf**: Similar to oneOf but validation allows multiple matches
203+
- **Approach**: Abstract base class with derived types, custom converters handle deserialization
204+
- **Untagged Issue**: Custom converters attempt deserialization in order
205+
206+
### Comparison with Rust
207+
208+
| Language | oneOf Implementation | anyOf Implementation | Untagged Handling |
209+
|----------|---------------------|---------------------|-------------------|
210+
| **Rust** | Untagged enum | Struct with optional fields | Serde's `#[serde(untagged)]` |
211+
| **Java** | Abstract class + adapters | Abstract class + adapters | Custom TypeAdapter |
212+
| **TypeScript** | Union type `A \| B` | Union type `A \| B` | Native support |
213+
| **Python** | Union with validation | Class with multiple validators | Custom validation |
214+
| **Go** | Struct with pointers | Struct with pointers | Custom UnmarshalJSON |
215+
| **C#** | Base class + converters | Base class + converters | Custom JsonConverter |
216+
217+
### Key Observations
218+
219+
1. **Type System Limitations**: Languages without union types (Java, C#, Go) use wrapper classes/structs
220+
2. **Runtime vs Compile Time**: Most languages handle this at runtime, Rust leverages Serde for compile-time generation
221+
3. **anyOf Semantics**: Only Rust and Python truly differentiate anyOf (multiple matches) from oneOf (single match)
222+
4. **Deserialization Order**: All implementations try options in order for untagged unions, which can lead to ambiguity
223+
165224
## Key Differences Summary
166225

167226
| Aspect | oneOf (XOR) | anyOf (OR) |

0 commit comments

Comments
 (0)