Skip to content

feat(gen): add value-based oneOf/anyOf discrimination#1588

Merged
ernado merged 4 commits intoogen-go:mainfrom
lanej:feature/value-based-discrimination
Nov 30, 2025
Merged

feat(gen): add value-based oneOf/anyOf discrimination#1588
ernado merged 4 commits intoogen-go:mainfrom
lanej:feature/value-based-discrimination

Conversation

@lanej
Copy link
Contributor

@lanej lanej commented Nov 27, 2025

Add value-based oneOf/anyOf discrimination

Problem

ogen currently rejects schemas where oneOf/anyOf variants have the same field name and JSON type, even when enum values are different:

oneOf:
  - properties:
      status: { enum: ["active", "pending"] }
  - properties:
      status: { enum: ["inactive", "deleted"] }

Error: type-based discrimination with same jxType

This blocks common patterns: status-based APIs, event systems with eventType enums, Kubernetes-style resource types, and state machines.

Solution

Adds value-based discrimination as a 5th automatic discrimination strategy. When variants share a field name and JSON type but have different enum values, ogen now:

  1. Finds a field with disjoint enum values across variants
  2. Skips fields with overlapping enum values if another field can discriminate
  3. Generates a switch statement on enum string values
  4. Automatically discriminates variants at runtime based on the enum value

Example generated code for status field:

case "status":
    value, _ := d.StrBytes()
    switch string(value) {
    case "active", "pending":
        s.Type = ActiveStatusResponse
    case "inactive", "deleted":
        s.Type = InactiveStatusResponse
    }

Overlapping Values Handling

When one field has overlapping enum values but another field can discriminate, ogen uses the discriminating field:

# Works: carrier field discriminates, signature overlap is ignored
oneOf:
  - properties:
      carrier: { enum: [usps] }
      signature: { enum: [gift, sample] }  # overlapping with FedEx
  - properties:
      carrier: { enum: [fedex] }
      signature: { enum: [gift, express] }  # overlapping with USPS

Testing

  • 3 positive scenarios: Basic enum discrimination, multiple discriminating fields, mixed discrimination strategies
  • Overlapping values scenario: When one field has overlapping enum values but another field can discriminate, uses the discriminating field (ShippingOption test case)
  • 1 negative scenario: Validates overlap detection with clear error messages when NO field can discriminate
  • Integration tests: All existing tests pass (2585+ core tests), new test suite added
  • Example tests: All pass including ex_github (GitHub API spec edge cases)

Changes

  • gen/ir/type.go: Added ValueDiscriminator metadata structure
  • gen/schema_gen_sum.go: Added detection and validation logic (~150 lines)
  • gen/_template/json/encoders_sum.tmpl: Added value discrimination template
  • gen/ir/faker.go: Fixed faker to skip parent fields that overlap with oneOf variant fields (prevents duplicate JSON keys)
  • README.md: Documented new discrimination strategy
  • Tests: Added comprehensive positive and negative test specs

Backward Compatibility

No breaking changes. Purely additive:

  • Schemas that previously failed now succeed
  • Schemas that previously worked continue working unchanged
  • All existing tests pass (2585+ core tests)

Related Issues

May help with #491 (anyOf discriminator support), #1230 (oneOf decode), #1480 (nested sum types).

Complements commit 40e0719 which added type-based discrimination.

Performance

String value checking is O(n) for n enum values, but:

  • Typical enum sets are 2-10 values (negligible impact)
  • Generated switch statements are compiler-optimized
  • Type-based discrimination still runs first (higher priority)

@lanej lanej marked this pull request as draft November 27, 2025 20:18
Add support for discriminating oneOf/anyOf schemas based on enum field
values when field names and JSON types are identical across variants.

This introduces a new automatic discrimination strategy that complements
the type-based discrimination added in 40e0719. When schemas share
field names with the same JSON type but different enum values, ogen now
generates efficient switch statements on those values instead of failing
with a type conflict error.

Changes:
- Add ValueDiscriminator IR metadata structure to capture enum-based
  discrimination patterns
- Implement detection and validation logic in schema_gen_sum.go to
  identify discriminable enum fields across oneOf/anyOf variants
- Generate value-based discrimination code via json/encoders_sum.tmpl
  template with proper nil returns after enum switches
- Fix Faker to skip parent fields that overlap with inline sum variant
  fields, preventing duplicate JSON key generation
- Document the new discrimination strategy in README.md
- Add comprehensive test coverage with positive (3 scenarios) and
  negative (overlap detection) specs

This is the 5th automatic discrimination strategy, enabling more
flexible schema designs without requiring explicit discriminator
mappings.
@lanej lanej force-pushed the feature/value-based-discrimination branch from 07e5a26 to a856421 Compare November 27, 2025 21:08
…minate

When multiple fields could potentially be used for value-based discrimination,
if one field has overlapping enum values across variants, continue checking
other fields instead of immediately failing. Only fail if NO field can
discriminate.

This fixes cases like:
- Carrier field with disjoint values (usps, fedex)
- Signature field with overlapping values (gift, sample in both)

Now ogen will use the carrier field for discrimination and ignore the
overlapping signature field.
@lanej lanej marked this pull request as ready for review November 28, 2025 17:21
@ernado ernado merged commit 14bb9c7 into ogen-go:main Nov 30, 2025
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants