Skip to content

Commit eb65e93

Browse files
authored
Fix Swift oneOf discriminator decoding with enumUnknownDefaultCase (#22635)
1 parent d90bfe0 commit eb65e93

File tree

4 files changed

+136
-6
lines changed

4 files changed

+136
-6
lines changed

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,41 @@
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
}
22+
{{#discriminator}}
23+
24+
private enum DiscriminatorCodingKey: String, CodingKey {
25+
case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}"
26+
}
27+
{{/discriminator}}
2228

2329
public init(from decoder: Decoder) throws {
24-
let container = try decoder.singleValueContainer()
30+
{{#discriminator}} let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)
31+
let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}})
32+
33+
switch discriminatorValue {
34+
{{#discriminator.mappedModels}}
35+
case "{{mappingName}}":
36+
self = .type{{modelName}}(try {{modelName}}(from: decoder))
37+
{{/discriminator.mappedModels}}
38+
default:
39+
{{#oneOfUnknownDefaultCase}}
40+
self = .unknownDefaultOpenApi
41+
{{/oneOfUnknownDefaultCase}}
42+
{{^oneOfUnknownDefaultCase}}
43+
throw DecodingError.dataCorrupted(
44+
DecodingError.Context(
45+
codingPath: decoder.codingPath,
46+
debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}"
47+
)
48+
)
49+
{{/oneOfUnknownDefaultCase}}
50+
}
51+
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
2552
{{#oneOf}}
2653
{{#-first}}
2754
if let value = try? container.decode({{.}}.self) {
@@ -39,5 +66,5 @@
3966
throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
4067
{{/oneOfUnknownDefaultCase}}
4168
}
42-
}
69+
{{/discriminator}} }
4370
}

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,41 @@
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
}
22+
{{#discriminator}}
23+
24+
private enum DiscriminatorCodingKey: String, CodingKey {
25+
case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}"
26+
}
27+
{{/discriminator}}
2228

2329
public init(from decoder: Decoder) throws {
24-
let container = try decoder.singleValueContainer()
30+
{{#discriminator}} let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)
31+
let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}})
32+
33+
switch discriminatorValue {
34+
{{#discriminator.mappedModels}}
35+
case "{{mappingName}}":
36+
self = .type{{#transformArrayType}}{{modelName}}{{/transformArrayType}}(try {{modelName}}(from: decoder))
37+
{{/discriminator.mappedModels}}
38+
default:
39+
{{#oneOfUnknownDefaultCase}}
40+
self = .unknownDefaultOpenApi
41+
{{/oneOfUnknownDefaultCase}}
42+
{{^oneOfUnknownDefaultCase}}
43+
throw DecodingError.dataCorrupted(
44+
DecodingError.Context(
45+
codingPath: decoder.codingPath,
46+
debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}"
47+
)
48+
)
49+
{{/oneOfUnknownDefaultCase}}
50+
}
51+
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
2552
{{#oneOf}}
2653
{{#-first}}
2754
if let value = try? container.decode({{.}}.self) {
@@ -39,5 +66,5 @@
3966
throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}"))
4067
{{/oneOfUnknownDefaultCase}}
4168
}
42-
}
69+
{{/discriminator}} }
4370
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/swift5/Swift5ClientCodegenTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,4 +317,42 @@ public void oneOfFormParameterTest() {
317317

318318
}
319319

320+
@Test(description = "test oneOf with discriminator generates discriminator-first decoding", enabled = true)
321+
public void oneOfDiscriminatorFirstDecodingTest() throws IOException {
322+
Path target = Files.createTempDirectory("test");
323+
File output = target.toFile();
324+
try {
325+
final CodegenConfigurator configurator = new CodegenConfigurator()
326+
.setGeneratorName("swift5")
327+
.setInputSpec("src/test/resources/3_0/oneOfDiscriminator.yaml")
328+
.setOutputDir(target.toAbsolutePath().toString());
329+
330+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
331+
DefaultGenerator generator = new DefaultGenerator(false);
332+
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
333+
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
334+
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
335+
336+
List<File> files = generator.opts(clientOptInput).generate();
337+
338+
File modelFile = files.stream()
339+
.filter(f -> f.getName().equals("FruitOneOfEnumMappingDisc.swift"))
340+
.findFirst()
341+
.orElseThrow(() -> new RuntimeException("FruitOneOfEnumMappingDisc.swift not found"));
342+
343+
String content = Files.readString(modelFile.toPath());
344+
345+
// Verify discriminator-first decoding pattern
346+
Assert.assertTrue(content.contains("private enum DiscriminatorCodingKey: String, CodingKey"));
347+
Assert.assertTrue(content.contains("let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)"));
348+
Assert.assertTrue(content.contains("switch discriminatorValue"));
349+
Assert.assertTrue(content.contains("case \"APPLE\":"));
350+
Assert.assertTrue(content.contains("self = .typeAppleOneOfEnumMappingDisc(try AppleOneOfEnumMappingDisc(from: decoder))"));
351+
Assert.assertFalse(content.contains("if let value = try? container.decode(AppleOneOfEnumMappingDisc.self)"));
352+
353+
} finally {
354+
output.deleteOnExit();
355+
}
356+
}
357+
320358
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/swift6/Swift6ClientCodegenTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,42 @@ public void oneOfArrayTypeNamesTest() throws IOException {
363363
output.deleteOnExit();
364364
}
365365
}
366+
367+
@Test(description = "test oneOf with discriminator generates discriminator-first decoding", enabled = true)
368+
public void oneOfDiscriminatorFirstDecodingTest() throws IOException {
369+
Path target = Files.createTempDirectory("test");
370+
File output = target.toFile();
371+
try {
372+
final CodegenConfigurator configurator = new CodegenConfigurator()
373+
.setGeneratorName("swift6")
374+
.setInputSpec("src/test/resources/3_0/oneOfDiscriminator.yaml")
375+
.setOutputDir(target.toAbsolutePath().toString());
376+
377+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
378+
DefaultGenerator generator = new DefaultGenerator(false);
379+
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
380+
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
381+
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false");
382+
383+
List<File> files = generator.opts(clientOptInput).generate();
384+
385+
File modelFile = files.stream()
386+
.filter(f -> f.getName().equals("FruitOneOfEnumMappingDisc.swift"))
387+
.findFirst()
388+
.orElseThrow(() -> new RuntimeException("FruitOneOfEnumMappingDisc.swift not found"));
389+
390+
String content = Files.readString(modelFile.toPath());
391+
392+
// Verify discriminator-first decoding pattern
393+
Assert.assertTrue(content.contains("private enum DiscriminatorCodingKey: String, CodingKey"));
394+
Assert.assertTrue(content.contains("let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self)"));
395+
Assert.assertTrue(content.contains("switch discriminatorValue"));
396+
Assert.assertTrue(content.contains("case \"APPLE\":"));
397+
Assert.assertTrue(content.contains("self = .typeAppleOneOfEnumMappingDisc(try AppleOneOfEnumMappingDisc(from: decoder))"));
398+
Assert.assertFalse(content.contains("if let value = try? container.decode(AppleOneOfEnumMappingDisc.self)"));
399+
400+
} finally {
401+
output.deleteOnExit();
402+
}
403+
}
366404
}

0 commit comments

Comments
 (0)