Skip to content

Conversation

@lastMove
Copy link

@lastMove lastMove commented Jan 8, 2026

Description

This PR implements discriminator-first decoding for oneOf schemas in Swift5 and Swift6 generators to fix issue #7549 where enumUnknownDefaultCase=true breaks discriminator-based routing.

Problem

When enumUnknownDefaultCase=true is set, discriminator fields in variant types have an unknownDefaultOpenApi fallback case. With the previous sequential try? decoding approach:

  1. Decoder tries the first variant (e.g., PredicateBetween)
  2. The discriminator field type doesn't match (e.g., "matchesAny" ≠ "between")
  3. BUT the unknownDefaultOpenApi fallback accepts it - decode succeeds!
  4. Wrong variant is selected with corrupted data
  5. Correct variant (e.g., PredicateMatchesAny) is never tried

Solution

Implement discriminator-first decoding:

  1. When discriminator exists: Read discriminator value FIRST using a keyed container, then switch on it to route directly to the correct variant
  2. When no discriminator: Use the original sequential try? approach (backward compatible)

Changes

Templates Modified:

  • modules/openapi-generator/src/main/resources/swift5/modelOneOf.mustache
  • modules/openapi-generator/src/main/resources/swift6/modelOneOf.mustache

Key Implementation Details:

  • Added DiscriminatorCodingKey enum for reading discriminator field
  • Conditional logic: discriminator-first vs sequential try?
  • Improved error messages that include actual discriminator value
  • Fixed encoding bug: removed unused parameter from unknownDefaultOpenApi case
  • Updated samples to reflect template changes

Example Generated Code (with discriminator):

public init(from decoder: Decoder) throws {
    // Discriminator-based decoding: read discriminator value first
    let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)
    let discriminatorValue = try keyedContainer.decode(String.self, forKey: .type)

    switch discriminatorValue {
    case "between":
        self = .typePredicateBetween(try PredicateBetween(from: decoder))
    case "matchesAny":
        self = .typePredicateMatchesAny(try PredicateMatchesAny(from: decoder))
    default:
        throw DecodingError.dataCorrupted(...)
    }
}

Benefits

  • ✅ Fixes discriminator-based oneOf decoding when enumUnknownDefaultCase=true
  • ✅ Better performance: O(1) switch vs O(n) sequential tries
  • ✅ Clearer error messages with actual discriminator values
  • ✅ No breaking changes - backward compatible for non-discriminator schemas
  • ✅ Follows Swift best practices for Codable

PR Checklist

  • Read the contribution guidelines
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work
  • Regenerated samples:
    ./mvnw clean install -DskipTests -Dmaven.javadoc.skip=true
    ./bin/generate-samples.sh bin/configs/swift5-oneOf.yaml
    ./bin/generate-samples.sh bin/configs/swift6-oneOf.yaml
    
    Committed all changed files including updated samples
  • Filed PR against the correct branch: master (non-breaking change)
  • PR solves a reported issue: fixes [BUG] [Swift5] support for oneOf, anyOf, allOf, and discriminator directives #7549
  • Mentioning technical committee: @4brunu

cc @4brunu - This PR implements discriminator support for Swift oneOf schemas as discussed in #7549. The implementation uses discriminator-first decoding to avoid the bug where unknownDefaultOpenApi fallback cases break discriminator routing. Backward compatibility is maintained for non-discriminator oneOf schemas.


Summary by cubic

Fix Swift oneOf decoding when enumUnknownDefaultCase=true by reading the discriminator first and routing to the correct variant. Keeps non-discriminator schemas using the original sequential decoding; fixes #7549.

  • Bug Fixes
    • Decode the discriminator via a keyed container and switch directly to the mapped variant.
    • Use sequential try? only when no discriminator is present.
    • For unknown discriminator values, return unknownDefaultOpenApi (if enabled) or throw with clearer error messages.
    • Remove the unused payload from unknownDefaultOpenApi during encoding.
    • Update Swift5/Swift6 modelOneOf templates and refresh samples.

Written for commit 96b6a88. Summary will update on new commits.

This commit implements discriminator-first decoding for oneOf schemas
in Swift5 and Swift6 generators to fix the bug where enumUnknownDefaultCase=true
breaks discriminator-based routing.

Problem:
When enumUnknownDefaultCase=true is set, discriminator fields have an
unknownDefaultOpenApi fallback case. With the previous sequential try?
decoding approach, the first variant always matched because the discriminator
field would accept any value via the fallback, causing incorrect type selection
and data corruption.

Solution:
- Implement discriminator-first decoding strategy
- When a discriminator exists, read its value FIRST using a keyed container
- Switch on the discriminator value to route directly to the correct variant
- Only use sequential try? decoding when NO discriminator is present

Changes:
- Modified swift5/modelOneOf.mustache to add discriminator support
- Modified swift6/modelOneOf.mustache to add discriminator support
- Added proper error messages that include the actual discriminator value
- Fixed encoding bug: removed unused parameter from unknownDefaultOpenApi case
- Maintained backward compatibility for non-discriminator oneOf schemas
- Updated samples to reflect template changes

Benefits:
- Fixes discriminator-based oneOf decoding when enumUnknownDefaultCase=true
- Better performance: O(1) switch vs O(n) sequential tries
- Clearer error messages with actual discriminator values
- No breaking changes for existing code

Fixes OpenAPITools#7549
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

@4brunu
Copy link
Contributor

4brunu commented Jan 8, 2026

Hi @lastMove, thanks for the contribution.
Did you tested those changes locally?

@lastMove
Copy link
Author

lastMove commented Jan 8, 2026

@4brunu yes I tested it, it works!
I think it would be better if I add a new test case for this. I will do that.

@4brunu
Copy link
Contributor

4brunu commented Jan 8, 2026

That would be nice, thanks

@lastMove lastMove force-pushed the fix-swift-oneof-discriminator-decoding branch from a17ea11 to d7d3422 Compare January 8, 2026 17:33
- Add 1 integration test for Swift5 generator
- Add 1 integration test for Swift6 generator
- Tests validate generated code uses discriminator-first decoding pattern
- Tests verify switch on discriminator value instead of sequential try?
@lastMove lastMove force-pushed the fix-swift-oneof-discriminator-decoding branch from d7d3422 to 96b6a88 Compare January 8, 2026 17:55
@4brunu
Copy link
Contributor

4brunu commented Jan 9, 2026

@lastMove can you merge master to your branch please? Thanks

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.

[BUG] [Swift5] support for oneOf, anyOf, allOf, and discriminator directives

2 participants