Skip to content

Commit 7cf2f6d

Browse files
committed
docs: add informal findings about wing328's test case
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.
1 parent 39f7a9e commit 7cf2f6d

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

docs/anyof-vs-oneof-true-semantics.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,28 @@ Your understanding is correct:
314314
- **anyOf** = "You can be ANY combination of these things"
315315
- **oneOf** = "You must be EXACTLY ONE of these things"
316316

317+
## Real-World Bug Example
318+
319+
Just discovered this while working with wing328's test case. The old Rust generator would literally generate broken code for this anyOf:
320+
321+
```yaml
322+
ModelIdentifier:
323+
anyOf:
324+
- type: string
325+
- type: string
326+
enum: [gpt-4, gpt-3.5-turbo]
327+
```
328+
329+
Old generator output:
330+
```rust
331+
pub enum ModelIdentifier {
332+
String(String), // Variant 1
333+
String(String), // Variant 2 - DUPLICATE NAME! Won't compile!
334+
}
335+
```
336+
337+
This is what happens when you treat anyOf (composition) as oneOf (choice) - you get nonsensical code. The correct approach generates a struct where both can coexist.
338+
317339
## Conclusion
318340

319341
You're absolutely right:

docs/rust-oneof-anyof-semantics.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,4 +403,67 @@ Previously, the Rust generator treated `anyOf` the same as `oneOf`, generating e
403403
To migrate existing code:
404404
- Replace enum pattern matching with struct field access
405405
- Use the `validate_any_of()` method to ensure at least one field is set
406-
- Access individual options via the `as_*` fields
406+
- Access individual options via the `as_*` fields
407+
408+
## Real Example: Wing328's Test Case
409+
410+
I merged wing328's PR #21911 which has a perfect test case showing the difference. Let me walk you through what I found:
411+
412+
### The Test Schema
413+
Wing328 created this anyOf schema:
414+
```yaml
415+
ModelIdentifier:
416+
description: Model identifier that can be a string or specific enum value
417+
anyOf:
418+
- type: string
419+
description: Any model name as string
420+
- type: string
421+
enum: [gpt-4, gpt-3.5-turbo, dall-e-3]
422+
description: Known model enum values
423+
```
424+
425+
### What the Old Generator Would Produce
426+
With the old (wrong) behavior, this would generate:
427+
```rust
428+
// OLD: Incorrectly treats anyOf as oneOf
429+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
430+
#[serde(untagged)]
431+
pub enum ModelIdentifier {
432+
String(String), // First string option
433+
String(String), // Second string option - DUPLICATE! This is broken!
434+
}
435+
```
436+
437+
See the problem? We'd have duplicate enum variants! The generator would actually produce invalid Rust code. Plus, even if it worked, you could only choose ONE option, not both.
438+
439+
### What Our New Generator Produces
440+
With the correct anyOf implementation:
441+
```rust
442+
// NEW: Correctly treats anyOf as composition
443+
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
444+
pub struct ModelIdentifier {
445+
#[serde(skip_serializing_if = "Option::is_none", rename = "as_any_of_0")]
446+
pub as_any_of_0: Option<String>, // Any model name
447+
448+
#[serde(skip_serializing_if = "Option::is_none", rename = "as_any_of_1")]
449+
pub as_any_of_1: Option<String>, // Known enum values
450+
}
451+
```
452+
453+
Now both fields can be set! This is actually useful - imagine an API that accepts both a freeform model name AND validates against known models. With anyOf, you can validate against both schemas simultaneously.
454+
455+
### Another Example from Wing328's Tests
456+
He also included this more complex anyOf:
457+
```yaml
458+
AnotherAnyOfTest:
459+
anyOf:
460+
- type: string
461+
- type: integer
462+
- type: array
463+
items:
464+
type: string
465+
```
466+
467+
Old behavior would force you to choose: "Is this a string OR an integer OR an array?"
468+
469+
New behavior lets you have all three! Maybe it's a weird API, but that's what anyOf means - the data can match multiple schemas at once. The generator shouldn't make assumptions about what's "sensible" - it should implement the spec correctly.

0 commit comments

Comments
 (0)