Skip to content

Commit ad666bd

Browse files
Merge branch 'swift-oneof-decoding-unknown-default-case'
2 parents 4a46c59 + 97c5c3b commit ad666bd

23 files changed

Lines changed: 258 additions & 6 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ 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+
extension UnknownCaseCheckable {
48+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool { false }
49+
}
50+
51+
{{/enumUnknownDefaultCase}}
3952
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
4053
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4154
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped: Hashable>: Hashable {

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,40 @@ 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}}{{#hasEnums}}
34+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
35+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
36+
{{#allVars}}
37+
{{#isEnum}}
38+
{{^isContainer}}
39+
{{#vendorExtensions.x-null-encodable}}
40+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
41+
{{/vendorExtensions.x-null-encodable}}
42+
{{^vendorExtensions.x-null-encodable}}
43+
if {{{name}}} == .unknownDefaultOpenApi { return true }
44+
{{/vendorExtensions.x-null-encodable}}
45+
{{/isContainer}}
46+
{{/isEnum}}
47+
{{^isEnum}}
48+
{{#isEnumRef}}
49+
{{^isContainer}}
50+
{{#vendorExtensions.x-null-encodable}}
51+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
52+
{{/vendorExtensions.x-null-encodable}}
53+
{{^vendorExtensions.x-null-encodable}}
54+
if {{{name}}} == .unknownDefaultOpenApi { return true }
55+
{{/vendorExtensions.x-null-encodable}}
56+
{{/isContainer}}
57+
{{/isEnumRef}}
58+
{{/isEnum}}
59+
{{/allVars}}
60+
return false
61+
}
62+
}
63+
{{/hasEnums}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{#isEnum}}
64+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
65+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
66+
self == .unknownDefaultOpenApi
67+
}
68+
}
69+
{{/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: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ 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+
extension UnknownCaseCheckable {
48+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool { false }
49+
}
50+
51+
{{/enumUnknownDefaultCase}}
3952
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
4053
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4154
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped> {

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,40 @@ 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}}{{#hasEnums}}
30+
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
31+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
32+
{{#allVars}}
33+
{{#isEnum}}
34+
{{^isContainer}}
35+
{{#vendorExtensions.x-null-encodable}}
36+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
37+
{{/vendorExtensions.x-null-encodable}}
38+
{{^vendorExtensions.x-null-encodable}}
39+
if {{{name}}} == .unknownDefaultOpenApi { return true }
40+
{{/vendorExtensions.x-null-encodable}}
41+
{{/isContainer}}
42+
{{/isEnum}}
43+
{{^isEnum}}
44+
{{#isEnumRef}}
45+
{{^isContainer}}
46+
{{#vendorExtensions.x-null-encodable}}
47+
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
48+
{{/vendorExtensions.x-null-encodable}}
49+
{{^vendorExtensions.x-null-encodable}}
50+
if {{{name}}} == .unknownDefaultOpenApi { return true }
51+
{{/vendorExtensions.x-null-encodable}}
52+
{{/isContainer}}
53+
{{/isEnumRef}}
54+
{{/isEnum}}
55+
{{/allVars}}
56+
return false
57+
}
58+
}
59+
{{/hasEnums}}{{/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/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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ 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+
45+
extension UnknownCaseCheckable {
46+
internal var containsUnknownDefaultOpenApiCase: Bool { false }
47+
}
48+
3849
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
3950
/// or not encoded (`.encodeNothing`). Intended for request payloads.
4051
internal enum NullEncodable<Wrapped: Hashable>: Hashable {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,10 @@ internal struct EnumArrays: Codable, JSONEncodable {
4444
}
4545
}
4646

47+
48+
extension EnumArrays: UnknownCaseCheckable {
49+
internal var containsUnknownDefaultOpenApiCase: Bool {
50+
if justSymbol == .unknownDefaultOpenApi { return true }
51+
return false
52+
}
53+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ internal enum EnumClass: String, Codable, CaseIterable, CaseIterableDefaultsLast
1616
case xyz = "(xyz)"
1717
case unknownDefaultOpenApi = "unknown_default_open_api"
1818
}
19+
20+
extension EnumClass: UnknownCaseCheckable {
21+
internal var containsUnknownDefaultOpenApiCase: Bool {
22+
self == .unknownDefaultOpenApi
23+
}
24+
}

0 commit comments

Comments
 (0)