Skip to content

Commit 21245d4

Browse files
committed
docs(rust): add comprehensive ambiguity handling documentation
- 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
1 parent af9ae0a commit 21245d4

File tree

1 file changed

+94
-0
lines changed

1 file changed

+94
-0
lines changed

docs/rust-oneof-anyof-semantics.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,100 @@ pub enum ShapeOneOf {
221221
3. **anyOf Semantics**: Only Rust and Python truly differentiate anyOf (multiple matches) from oneOf (single match)
222222
4. **Deserialization Order**: All implementations try options in order for untagged unions, which can lead to ambiguity
223223

224+
## Ambiguity Handling Strategies
225+
226+
### Do Any Languages Refuse to Generate?
227+
228+
**No language generator completely refuses to generate code for ambiguous schemas.** All have fallback strategies:
229+
230+
### Language-Specific Ambiguity Handling
231+
232+
#### **Swift**
233+
- **Strategy**: Provides `oneOfUnknownDefaultCase` option
234+
- **Behavior**: Can generate an `unknownDefaultOpenApi` case for unmatched values
235+
- **Without Option**: Throws `DecodingError.typeMismatch` at runtime
236+
- **Philosophy**: Fail at runtime rather than compile time
237+
238+
#### **Python (Pydantic)**
239+
- **Strategy**: Generates validation code with `ValidationError`
240+
- **Behavior**: Validates all options and tracks which ones match
241+
- **For oneOf**: Ensures exactly one matches, raises `ValidationError` if multiple match
242+
- **Philosophy**: Strict runtime validation with clear error messages
243+
244+
#### **Java**
245+
- **Strategy**: Custom TypeAdapters try each type sequentially
246+
- **Behavior**: First successful deserialization wins
247+
- **Ambiguity**: No validation that only one matches for oneOf
248+
- **Philosophy**: Pragmatic "first match wins" approach
249+
250+
#### **TypeScript**
251+
- **Strategy**: Union types with no runtime validation by default
252+
- **Behavior**: Structural typing means any matching shape is accepted
253+
- **Ambiguity**: Completely permissive - type system doesn't enforce exclusivity
254+
- **Philosophy**: Trust the data or add runtime validation separately
255+
256+
#### **Go**
257+
- **Strategy**: Custom UnmarshalJSON tries to populate all fields
258+
- **Behavior**: For oneOf, additional validation ensures only one is non-nil
259+
- **Ambiguity**: Returns error if multiple match for oneOf
260+
- **Philosophy**: Explicit validation after unmarshaling
261+
262+
#### **Rust**
263+
- **Strategy**: Untagged enums for oneOf, struct with options for anyOf
264+
- **Behavior**: Serde tries variants in order (first match wins for oneOf)
265+
- **Ambiguity**: No compile-time detection of overlapping variants
266+
- **Philosophy**: Leverage existing serialization framework
267+
268+
### Common Warnings and Limitations
269+
270+
From the OpenAPI Generator codebase:
271+
272+
1. **Self-referencing schemas**: Detected and removed to prevent infinite loops
273+
2. **Inline objects in oneOf with discriminator**: Warned and ignored
274+
3. **Conflicting composition**: Error logged when schema has incorrect anyOf/allOf/oneOf combination
275+
4. **Missing discriminator**: Most generators work but with "first match wins" semantics
276+
277+
### Best Practices for Avoiding Ambiguity
278+
279+
1. **Use discriminators**: When possible, add a discriminator property for oneOf
280+
```yaml
281+
oneOf:
282+
- $ref: '#/components/schemas/Cat'
283+
- $ref: '#/components/schemas/Dog'
284+
discriminator:
285+
propertyName: petType
286+
```
287+
288+
2. **Make schemas mutually exclusive**: Design schemas that don't overlap
289+
```yaml
290+
oneOf:
291+
- type: object
292+
required: [foo]
293+
properties:
294+
foo: {type: string}
295+
- type: object
296+
required: [bar]
297+
properties:
298+
bar: {type: number}
299+
```
300+
301+
3. **Order matters**: Place more specific schemas first
302+
```yaml
303+
oneOf:
304+
- type: object
305+
required: [a, b, c] # More specific
306+
- type: object
307+
required: [a] # Less specific
308+
```
309+
310+
### Why Don't Generators Refuse?
311+
312+
1. **Pragmatism**: Real-world APIs often have imperfect schemas
313+
2. **Backwards compatibility**: Existing APIs shouldn't break
314+
3. **Runtime nature**: Many ambiguities only manifest with specific data
315+
4. **User choice**: Developers can add additional validation if needed
316+
5. **OpenAPI spec**: The spec itself doesn't forbid ambiguous schemas
317+
224318
## Key Differences Summary
225319
226320
| Aspect | oneOf (XOR) | anyOf (OR) |

0 commit comments

Comments
 (0)