Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bin/configs/rust-hyper-anyof.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
generatorName: rust
outputDir: samples/client/others/rust/hyper/anyof
library: hyper
inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/rust-anyof-test.yaml
templateDir: modules/openapi-generator/src/main/resources/rust
additionalProperties:
supportAsync: false
packageName: anyof-hyper
346 changes: 346 additions & 0 deletions docs/anyof-vs-oneof-true-semantics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
# The Complete Picture: allOf vs anyOf vs oneOf

## The Three Composition Keywords - Finally Clear!

### allOf = Required Composition (Must Match ALL)
**allOf requires the object to be valid against ALL schemas simultaneously - it's a mandatory merge.**

```yaml
# allOf Example - Required Composition
Employee:
allOf:
- $ref: '#/components/schemas/Person'
- $ref: '#/components/schemas/Worker'
- type: object
required: [employeeId]
properties:
employeeId:
type: string

Person:
type: object
required: [name, age]
properties:
name:
type: string
age:
type: integer

Worker:
type: object
required: [position, salary]
properties:
position:
type: string
salary:
type: number
```

**Valid allOf instance (MUST have all required fields):**
```json
{
"name": "John Doe", // Required from Person
"age": 30, // Required from Person
"position": "Engineer", // Required from Worker
"salary": 100000, // Required from Worker
"employeeId": "EMP001" // Required from inline schema
}
```

**Correct Rust Implementation:**
```rust
// allOf: Struct with ALL required fields
pub struct Employee {
// From Person (required)
pub name: String,
pub age: i32,

// From Worker (required)
pub position: String,
pub salary: f64,

// From inline schema (required)
pub employee_id: String,
}
```

### anyOf = Optional Composition (Match ONE OR MORE)
**anyOf is about composing a new object from multiple schemas that can all be valid simultaneously.**

```yaml
# anyOf Example - Object Composition
PersonWithEmployment:
anyOf:
- $ref: '#/components/schemas/PersonalInfo'
- $ref: '#/components/schemas/EmploymentInfo'
- $ref: '#/components/schemas/EducationInfo'

# Components
PersonalInfo:
type: object
properties:
name:
type: string
age:
type: integer

EmploymentInfo:
type: object
properties:
company:
type: string
position:
type: string

EducationInfo:
type: object
properties:
degree:
type: string
university:
type: string
```

**Valid anyOf instance (combines all three):**
```json
{
"name": "John Doe",
"age": 30,
"company": "TechCorp",
"position": "Engineer",
"degree": "MS Computer Science",
"university": "MIT"
}
```

This object is valid against PersonalInfo AND EmploymentInfo AND EducationInfo simultaneously!

### oneOf = Choice (Either/Or)
**oneOf is about choosing exactly one option from mutually exclusive alternatives.**

```yaml
# oneOf Example - Mutually Exclusive Choice
PaymentMethod:
oneOf:
- $ref: '#/components/schemas/CreditCard'
- $ref: '#/components/schemas/BankTransfer'
- $ref: '#/components/schemas/PayPal'

# Components with discriminating required fields
CreditCard:
type: object
required: [cardNumber, cvv]
properties:
cardNumber:
type: string
cvv:
type: string

BankTransfer:
type: object
required: [accountNumber, routingNumber]
properties:
accountNumber:
type: string
routingNumber:
type: string

PayPal:
type: object
required: [paypalEmail]
properties:
paypalEmail:
type: string
```

**Valid oneOf instance (exactly one):**
```json
{
"cardNumber": "1234-5678-9012-3456",
"cvv": "123"
}
```

## The Fundamental Difference

### anyOf = Additive/Compositional (AND logic)
- **Purpose**: Combine properties from multiple schemas
- **Validation**: Can validate against multiple schemas
- **Use Case**: Mixins, traits, optional feature sets
- **Think**: "This object can have properties from schema A AND schema B AND schema C"

### oneOf = Selective/Exclusive (XOR logic)
- **Purpose**: Choose one schema from alternatives
- **Validation**: Must validate against exactly one schema
- **Use Case**: Polymorphic types, variant records
- **Think**: "This object is EITHER type A OR type B OR type C"

## What This Means for Implementations

### Correct anyOf Implementation (Composition)

```rust
// CORRECT: Struct that can compose multiple schemas
pub struct PersonWithEmployment {
// From PersonalInfo
pub name: Option<String>,
pub age: Option<i32>,

// From EmploymentInfo
pub company: Option<String>,
pub position: Option<String>,

// From EducationInfo
pub degree: Option<String>,
pub university: Option<String>,
}
```

### Current (Wrong) anyOf Implementation in Most Generators

```rust
// WRONG: Treats anyOf like oneOf (choice instead of composition)
pub enum PersonWithEmployment {
PersonalInfo(PersonalInfo), // Wrong! Can only be one
EmploymentInfo(EmploymentInfo), // Should be able to combine
EducationInfo(EducationInfo), // All three simultaneously
}
```

## Real-World Example: API Response

```yaml
# anyOf for composition - response can have data AND/OR errors AND/OR warnings
ApiResponse:
anyOf:
- type: object
properties:
data:
type: object
- type: object
properties:
errors:
type: array
items:
type: string
- type: object
properties:
warnings:
type: array
items:
type: string
```

**Valid response (has all three):**
```json
{
"data": { "id": 123, "name": "Success" },
"errors": [],
"warnings": ["Deprecated endpoint"]
}
```

## The Problem with Current Implementations

Most generators (except Python and our new Rust approach) treat anyOf like oneOf:

| Generator | anyOf Implementation | Correct? |
|-----------|---------------------|----------|
| TypeScript | Union type `A \| B` | ❌ No - can't compose |
| Java | Abstract class with one active | ❌ No - can't compose |
| Old Rust | Enum (one variant) | ❌ No - can't compose |
| Python | Validates all, keeps track | ✅ Yes - true composition |
| New Rust | Struct with optional fields | ✅ Yes - true composition |

## The Complete Summary

### The Three Keywords - Correct Semantics

| Keyword | Semantics | Validation | Correct Implementation | Logic Type |
|---------|-----------|------------|------------------------|------------|
| **allOf** | Required composition | Must match ALL schemas | Struct with all required fields merged | AND (mandatory) |
| **anyOf** | Optional composition | Must match ONE OR MORE schemas | Struct with optional fields from all schemas | OR (inclusive) |
| **oneOf** | Exclusive choice | Must match EXACTLY ONE schema | Enum with variants | XOR (exclusive) |

### Correct Rust Implementations

```rust
// allOf: Everything required
pub struct FullEmployee {
pub name: String, // Required from Person
pub age: i32, // Required from Person
pub position: String, // Required from Worker
pub salary: f64, // Required from Worker
pub employee_id: String, // Required from additional
}

// anyOf: Optional composition
pub struct FlexibleEmployee {
pub name: Option<String>, // Can have Person fields
pub age: Option<i32>,
pub position: Option<String>, // Can have Worker fields
pub salary: Option<f64>,
pub employee_id: Option<String>, // Can have additional fields
// Can have any combination!
}

// oneOf: Exclusive choice
pub enum EmployeeType {
Contractor(Contractor), // Either contractor
FullTime(FullTime), // OR full-time
Intern(Intern), // OR intern
// Exactly one!
}
```

### The Key Insight

You've identified the fundamental pattern:

| | Composition? | Required? | Result Type |
|---|---|---|---|
| **allOf** | ✅ Yes | ✅ All fields required | Merged struct |
| **anyOf** | ✅ Yes | ❌ Fields optional | Struct with options |
| **oneOf** | ❌ No | N/A (choice) | Enum/Union |

### Why This Matters

Most generators get anyOf wrong because they treat it as a choice (like oneOf) instead of composition:
- **Wrong**: anyOf as enum/union (can only have one)
- **Right**: anyOf as struct with optional fields (can have multiple)

Your understanding is correct:
- **allOf** = "You must be ALL of these things"
- **anyOf** = "You can be ANY combination of these things"
- **oneOf** = "You must be EXACTLY ONE of these things"

## Real-World Bug Example

Just discovered this while working with wing328's test case. The old Rust generator would literally generate broken code for this anyOf:

```yaml
ModelIdentifier:
anyOf:
- type: string
- type: string
enum: [gpt-4, gpt-3.5-turbo]
```

Old generator output:
```rust
pub enum ModelIdentifier {
String(String), // Variant 1
String(String), // Variant 2 - DUPLICATE NAME! Won't compile!
}
```

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.

## Conclusion

You're absolutely right:
- **allOf** = Required composition (struct with required fields)
- **anyOf** = Optional composition (struct with optional fields)
- **oneOf** = Exclusive choice (enum)

Most implementations conflate anyOf with oneOf, missing that anyOf is about composition, not choice!
Loading
Loading