Skip to content

Commit 6f6f05c

Browse files
committed
Fix Swift oneOf discriminator decoding with enumUnknownDefaultCase
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 #7549
1 parent 753330d commit 6f6f05c

4 files changed

Lines changed: 72 additions & 2 deletions

File tree

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,45 @@
1414
try container.encode(value)
1515
{{/oneOf}}
1616
{{#oneOfUnknownDefaultCase}}
17-
case unknownDefaultOpenApi(let type):
17+
case .unknownDefaultOpenApi:
1818
try container.encodeNil()
1919
{{/oneOfUnknownDefaultCase}}
2020
}
2121
}
2222

23+
{{#discriminator}}
24+
private enum DiscriminatorCodingKey: String, CodingKey {
25+
case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}"
26+
}
27+
{{/discriminator}}
28+
2329
public init(from decoder: Decoder) throws {
30+
{{#discriminator}}
31+
// Discriminator-based decoding: read discriminator value first, then decode the correct variant
32+
let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)
33+
let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}})
34+
35+
switch discriminatorValue {
36+
{{#discriminator.mappedModels}}
37+
case "{{mappingName}}":
38+
self = .type{{modelName}}(try {{modelName}}(from: decoder))
39+
{{/discriminator.mappedModels}}
40+
default:
41+
{{#oneOfUnknownDefaultCase}}
42+
self = .unknownDefaultOpenApi
43+
{{/oneOfUnknownDefaultCase}}
44+
{{^oneOfUnknownDefaultCase}}
45+
throw DecodingError.dataCorrupted(
46+
DecodingError.Context(
47+
codingPath: decoder.codingPath,
48+
debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}"
49+
)
50+
)
51+
{{/oneOfUnknownDefaultCase}}
52+
}
53+
{{/discriminator}}
54+
{{^discriminator}}
55+
// No discriminator: try each type sequentially
2456
let container = try decoder.singleValueContainer()
2557
{{#oneOf}}
2658
{{#-first}}
@@ -39,5 +71,6 @@
3971
throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
4072
{{/oneOfUnknownDefaultCase}}
4173
}
74+
{{/discriminator}}
4275
}
4376
}

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,45 @@
1414
try container.encode(value)
1515
{{/oneOf}}
1616
{{#oneOfUnknownDefaultCase}}
17-
case unknownDefaultOpenApi(let type):
17+
case .unknownDefaultOpenApi:
1818
try container.encodeNil()
1919
{{/oneOfUnknownDefaultCase}}
2020
}
2121
}
2222

23+
{{#discriminator}}
24+
private enum DiscriminatorCodingKey: String, CodingKey {
25+
case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}"
26+
}
27+
{{/discriminator}}
28+
2329
public init(from decoder: Decoder) throws {
30+
{{#discriminator}}
31+
// Discriminator-based decoding: read discriminator value first, then decode the correct variant
32+
let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)
33+
let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}})
34+
35+
switch discriminatorValue {
36+
{{#discriminator.mappedModels}}
37+
case "{{mappingName}}":
38+
self = .type{{#transformArrayType}}{{modelName}}{{/transformArrayType}}(try {{modelName}}(from: decoder))
39+
{{/discriminator.mappedModels}}
40+
default:
41+
{{#oneOfUnknownDefaultCase}}
42+
self = .unknownDefaultOpenApi
43+
{{/oneOfUnknownDefaultCase}}
44+
{{^oneOfUnknownDefaultCase}}
45+
throw DecodingError.dataCorrupted(
46+
DecodingError.Context(
47+
codingPath: decoder.codingPath,
48+
debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}"
49+
)
50+
)
51+
{{/oneOfUnknownDefaultCase}}
52+
}
53+
{{/discriminator}}
54+
{{^discriminator}}
55+
// No discriminator: try each type sequentially
2456
let container = try decoder.singleValueContainer()
2557
{{#oneOf}}
2658
{{#-first}}
@@ -39,5 +71,6 @@
3971
throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
4072
{{/oneOfUnknownDefaultCase}}
4173
}
74+
{{/discriminator}}
4275
}
4376
}

samples/client/petstore/swift5/oneOf/PetstoreClient/Classes/OpenAPIs/Models/Fruit.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ public enum Fruit: Codable, JSONEncodable, Hashable {
2727
}
2828
}
2929

30+
3031
public init(from decoder: Decoder) throws {
32+
// No discriminator: try each type sequentially
3133
let container = try decoder.singleValueContainer()
3234
if let value = try? container.decode(Apple.self) {
3335
self = .typeApple(value)

samples/client/petstore/swift6/oneOf/PetstoreClient/Classes/OpenAPIs/Models/Fruit.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public enum Fruit: Sendable, Codable, ParameterConvertible, Hashable {
2424
}
2525
}
2626

27+
2728
public init(from decoder: Decoder) throws {
29+
// No discriminator: try each type sequentially
2830
let container = try decoder.singleValueContainer()
2931
if let value = try? container.decode(Apple.self) {
3032
self = .typeApple(value)

0 commit comments

Comments
 (0)