Skip to content

Commit e99dbc1

Browse files
Fix oneOf decoding when enumUnknownDefaultCase is enabled
1 parent 7973088 commit e99dbc1

83 files changed

Lines changed: 598 additions & 6 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ extension CaseIterableDefaultsLast {
3636
}
3737
}
3838

39+
{{#enumUnknownDefaultCase}}
40+
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
41+
/// a variant that only decoded successfully because CaseIterableDefaultsLast
42+
/// silently accepted an unknown enum value.
43+
protocol UnknownCaseCheckable {
44+
var containsUnknownDefaultOpenApiCase: Bool { get }
45+
}
46+
47+
{{/enumUnknownDefaultCase}}
3948
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
4049
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4150
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped: Hashable>: Hashable {

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,36 @@ extension {{projectName}}API {
3030
{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}}
3131
@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)
3232
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {}
33-
{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}}
33+
{{/vendorExtensions.x-swift-identifiable}}{{#enumUnknownDefaultCase}}{{^vendorExtensions.x-is-one-of-interface}}{{^isArray}}{{^isEnum}}
34+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
35+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
36+
{{#allVars}}
37+
{{#isEnum}}
38+
{{#vendorExtensions.x-null-encodable}}
39+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
40+
{{/vendorExtensions.x-null-encodable}}
41+
{{^vendorExtensions.x-null-encodable}}
42+
if {{{name}}} == .unknownDefaultOpenApi { return true }
43+
{{/vendorExtensions.x-null-encodable}}
44+
{{/isEnum}}
45+
{{^isEnum}}
46+
{{#isEnumRef}}
47+
{{#vendorExtensions.x-null-encodable}}
48+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
49+
{{/vendorExtensions.x-null-encodable}}
50+
{{^vendorExtensions.x-null-encodable}}
51+
if {{{name}}} == .unknownDefaultOpenApi { return true }
52+
{{/vendorExtensions.x-null-encodable}}
53+
{{/isEnumRef}}
54+
{{/isEnum}}
55+
{{/allVars}}
56+
return false
57+
}
58+
}
59+
{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{#isEnum}}
60+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
61+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
62+
self == .unknownDefaultOpenApi
63+
}
64+
}
65+
{{/isEnum}}{{/enumUnknownDefaultCase}}{{/model}}{{/models}}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
5252
{{#oneOf}}
5353
{{#-first}}
54-
if let value = try? container.decode({{.}}.self) {
54+
if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
5555
{{/-first}}
5656
{{^-first}}
57-
} else if let value = try? container.decode({{.}}.self) {
57+
} else if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
5858
{{/-first}}
5959
self = .type{{.}}(value)
6060
{{/oneOf}}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ extension CaseIterableDefaultsLast {
3636
}
3737
}
3838

39+
{{#enumUnknownDefaultCase}}
40+
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
41+
/// a variant that only decoded successfully because CaseIterableDefaultsLast
42+
/// silently accepted an unknown enum value.
43+
protocol UnknownCaseCheckable {
44+
var containsUnknownDefaultOpenApiCase: Bool { get }
45+
}
46+
47+
{{/enumUnknownDefaultCase}}
3948
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
4049
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4150
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped> {

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,36 @@ extension {{projectName}}API {
2626
}
2727
{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}}
2828
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {}
29-
{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}}
29+
{{/vendorExtensions.x-swift-identifiable}}{{#enumUnknownDefaultCase}}{{^vendorExtensions.x-is-one-of-interface}}{{^isArray}}{{^isEnum}}
30+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
31+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
32+
{{#allVars}}
33+
{{#isEnum}}
34+
{{#vendorExtensions.x-null-encodable}}
35+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
36+
{{/vendorExtensions.x-null-encodable}}
37+
{{^vendorExtensions.x-null-encodable}}
38+
if {{{name}}} == .unknownDefaultOpenApi { return true }
39+
{{/vendorExtensions.x-null-encodable}}
40+
{{/isEnum}}
41+
{{^isEnum}}
42+
{{#isEnumRef}}
43+
{{#vendorExtensions.x-null-encodable}}
44+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
45+
{{/vendorExtensions.x-null-encodable}}
46+
{{^vendorExtensions.x-null-encodable}}
47+
if {{{name}}} == .unknownDefaultOpenApi { return true }
48+
{{/vendorExtensions.x-null-encodable}}
49+
{{/isEnumRef}}
50+
{{/isEnum}}
51+
{{/allVars}}
52+
return false
53+
}
54+
}
55+
{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{#isEnum}}
56+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
57+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
58+
self == .unknownDefaultOpenApi
59+
}
60+
}
61+
{{/isEnum}}{{/enumUnknownDefaultCase}}{{/model}}{{/models}}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
5252
{{#oneOf}}
5353
{{#-first}}
54-
if let value = try? container.decode({{.}}.self) {
54+
if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
5555
{{/-first}}
5656
{{^-first}}
57-
} else if let value = try? container.decode({{.}}.self) {
57+
} else if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
5858
{{/-first}}
5959
self = .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(value)
6060
{{/oneOf}}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,38 @@ public void oneOfArrayTypeNamesTest() throws IOException {
364364
}
365365
}
366366

367+
@Test(description = "test oneOf with enumUnknownDefaultCase generates UnknownCaseCheckable guard", enabled = true)
368+
public void oneOfEnumUnknownDefaultCaseGuardTest() 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/oneOf.yaml")
375+
.setOutputDir(target.toAbsolutePath().toString())
376+
.addAdditionalProperty("enumUnknownDefaultCase", true);
377+
378+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
379+
DefaultGenerator generator = new DefaultGenerator(false);
380+
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
381+
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
382+
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "true");
383+
384+
List<File> files = generator.opts(clientOptInput).generate();
385+
386+
String oneOfContent = Files.readString(files.stream()
387+
.filter(f -> f.getName().equals("Fruit.swift")).findFirst().get().toPath());
388+
Assert.assertTrue(oneOfContent.contains("as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true"),
389+
"oneOf decoder should guard against unknown default enum cases");
390+
391+
String modelsContent = Files.readString(files.stream()
392+
.filter(f -> f.getName().equals("Models.swift")).findFirst().get().toPath());
393+
Assert.assertTrue(modelsContent.contains("protocol UnknownCaseCheckable"));
394+
} finally {
395+
output.deleteOnExit();
396+
}
397+
}
398+
367399
@Test(description = "test oneOf with discriminator generates discriminator-first decoding", enabled = true)
368400
public void oneOfDiscriminatorFirstDecodingTest() throws IOException {
369401
Path target = Files.createTempDirectory("test");

samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/Models.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ extension CaseIterableDefaultsLast {
3535
}
3636
}
3737

38+
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
39+
/// a variant that only decoded successfully because CaseIterableDefaultsLast
40+
/// silently accepted an unknown enum value.
41+
protocol UnknownCaseCheckable {
42+
var containsUnknownDefaultOpenApiCase: Bool { get }
43+
}
44+
3845
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
3946
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4047
internal enum NullEncodable<Wrapped: Hashable>: Hashable {

samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/Models/AdditionalPropertiesClass.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ internal struct AdditionalPropertiesClass: Codable, JSONEncodable {
3434
}
3535
}
3636

37+
38+
extension AdditionalPropertiesClass: UnknownCaseCheckable {
39+
internal var containsUnknownDefaultOpenApiCase: Bool {
40+
return false
41+
}
42+
}

samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/Models/Animal.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ internal struct Animal: Codable, JSONEncodable {
3434
}
3535
}
3636

37+
38+
extension Animal: UnknownCaseCheckable {
39+
internal var containsUnknownDefaultOpenApiCase: Bool {
40+
return false
41+
}
42+
}

0 commit comments

Comments
 (0)