Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ extension CaseIterableDefaultsLast {
}
}

{{#enumUnknownDefaultCase}}
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
/// a variant that only decoded successfully because CaseIterableDefaultsLast
/// silently accepted an unknown enum value.
protocol UnknownCaseCheckable {
var containsUnknownDefaultOpenApiCase: Bool { get }
}

extension UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool { false }
}

{{/enumUnknownDefaultCase}}
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
/// or not encoded (`.encodeNothing`). Intended for request payloads.
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped: Hashable>: Hashable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,40 @@ extension {{projectName}}API {
{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}}
@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {}
{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}}
{{/vendorExtensions.x-swift-identifiable}}{{#enumUnknownDefaultCase}}{{^vendorExtensions.x-is-one-of-interface}}{{^isArray}}{{^isEnum}}{{#hasEnums}}
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
{{#allVars}}
{{#isEnum}}
{{^isContainer}}
{{#vendorExtensions.x-null-encodable}}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

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

P2: containsUnknownDefaultOpenApiCase skips enum containers, so unknown enum values inside arrays/maps are not detected and oneOf decoding can still accept the wrong variant.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/swift5/model.mustache, line 39:

<comment>containsUnknownDefaultOpenApiCase skips enum containers, so unknown enum values inside arrays/maps are not detected and oneOf decoding can still accept the wrong variant.</comment>

<file context>
@@ -30,4 +30,40 @@ extension {{projectName}}API {
+        {{#allVars}}
+        {{#isEnum}}
+        {{^isContainer}}
+        {{#vendorExtensions.x-null-encodable}}
+        if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
+        {{/vendorExtensions.x-null-encodable}}
</file context>
Fix with Cubic

if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{/isEnum}}
{{^isEnum}}
{{#isEnumRef}}
{{^isContainer}}
{{#vendorExtensions.x-null-encodable}}
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{/isEnumRef}}
{{/isEnum}}
{{/allVars}}
return false
}
}
{{/hasEnums}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{#isEnum}}
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
{{/isEnum}}{{/enumUnknownDefaultCase}}{{/model}}{{/models}}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
{{#oneOf}}
{{#-first}}
if let value = try? container.decode({{.}}.self) {
if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
{{/-first}}
{{^-first}}
} else if let value = try? container.decode({{.}}.self) {
} else if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
{{/-first}}
self = .type{{.}}(value)
{{/oneOf}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ extension CaseIterableDefaultsLast {
}
}

{{#enumUnknownDefaultCase}}
/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
/// a variant that only decoded successfully because CaseIterableDefaultsLast
/// silently accepted an unknown enum value.
protocol UnknownCaseCheckable {
var containsUnknownDefaultOpenApiCase: Bool { get }
}

extension UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool { false }
}

{{/enumUnknownDefaultCase}}
/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
/// or not encoded (`.encodeNothing`). Intended for request payloads.
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable<Wrapped> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,40 @@ extension {{projectName}}API {
}
{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}}
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {}
{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}}
{{/vendorExtensions.x-swift-identifiable}}{{#enumUnknownDefaultCase}}{{^vendorExtensions.x-is-one-of-interface}}{{^isArray}}{{^isEnum}}{{#hasEnums}}
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
{{#allVars}}
{{#isEnum}}
{{^isContainer}}
{{#vendorExtensions.x-null-encodable}}
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

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

P2: Unknown-case detection skips enums inside containers, so oneOf decoding can still accept a variant even when enum elements fell back to .unknownDefaultOpenApi.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/swift6/model.mustache, line 35:

<comment>Unknown-case detection skips enums inside containers, so oneOf decoding can still accept a variant even when enum elements fell back to `.unknownDefaultOpenApi`.</comment>

<file context>
@@ -26,4 +26,40 @@ extension {{projectName}}API {
+        {{#allVars}}
+        {{#isEnum}}
+        {{^isContainer}}
+        {{#vendorExtensions.x-null-encodable}}
+        if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
+        {{/vendorExtensions.x-null-encodable}}
</file context>
Fix with Cubic

if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{/isEnum}}
{{^isEnum}}
{{#isEnumRef}}
{{^isContainer}}
{{#vendorExtensions.x-null-encodable}}
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{/isEnumRef}}
{{/isEnum}}
{{/allVars}}
return false
}
}
{{/hasEnums}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{#isEnum}}
extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: UnknownCaseCheckable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
{{/isEnum}}{{/enumUnknownDefaultCase}}{{/model}}{{/models}}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer()
{{#oneOf}}
{{#-first}}
if let value = try? container.decode({{.}}.self) {
if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
{{/-first}}
{{^-first}}
} else if let value = try? container.decode({{.}}.self) {
} else if let value = try? container.decode({{.}}.self){{#enumUnknownDefaultCase}}, (value as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true{{/enumUnknownDefaultCase}} {
{{/-first}}
self = .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(value)
{{/oneOf}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,38 @@ public void oneOfArrayTypeNamesTest() throws IOException {
}
}

@Test(description = "test oneOf with enumUnknownDefaultCase generates UnknownCaseCheckable guard", enabled = true)
public void oneOfEnumUnknownDefaultCaseGuardTest() throws IOException {
Path target = Files.createTempDirectory("test");
File output = target.toFile();
try {
final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("swift6")
.setInputSpec("src/test/resources/3_0/oneOf.yaml")
.setOutputDir(target.toAbsolutePath().toString())
.addAdditionalProperty("enumUnknownDefaultCase", true);

final ClientOptInput clientOptInput = configurator.toClientOptInput();
DefaultGenerator generator = new DefaultGenerator(false);
generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true");
generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false");
generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "true");

List<File> files = generator.opts(clientOptInput).generate();

String oneOfContent = Files.readString(files.stream()
.filter(f -> f.getName().equals("Fruit.swift")).findFirst().get().toPath());
Assert.assertTrue(oneOfContent.contains("as? UnknownCaseCheckable)?.containsUnknownDefaultOpenApiCase != true"),
"oneOf decoder should guard against unknown default enum cases");

String modelsContent = Files.readString(files.stream()
.filter(f -> f.getName().equals("Models.swift")).findFirst().get().toPath());
Assert.assertTrue(modelsContent.contains("protocol UnknownCaseCheckable"));
} finally {
output.deleteOnExit();
}
}

@Test(description = "test oneOf with discriminator generates discriminator-first decoding", enabled = true)
public void oneOfDiscriminatorFirstDecodingTest() throws IOException {
Path target = Files.createTempDirectory("test");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ extension CaseIterableDefaultsLast {
}
}

/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
/// a variant that only decoded successfully because CaseIterableDefaultsLast
/// silently accepted an unknown enum value.
protocol UnknownCaseCheckable {
var containsUnknownDefaultOpenApiCase: Bool { get }
}

extension UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool { false }
}

/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
/// or not encoded (`.encodeNothing`). Intended for request payloads.
internal enum NullEncodable<Wrapped: Hashable>: Hashable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,10 @@ internal struct EnumArrays: Codable, JSONEncodable {
}
}


extension EnumArrays: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if justSymbol == .unknownDefaultOpenApi { return true }
return false
}
Comment on lines +50 to +52
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

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

P2: UnknownCaseCheckable ignores arrayEnum, so unknown enum defaults inside arrayEnum are not detected.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/swift5/resultLibrary/PetstoreClient/Classes/OpenAPIs/Models/EnumArrays.swift, line 50:

<comment>UnknownCaseCheckable ignores arrayEnum, so unknown enum defaults inside arrayEnum are not detected.</comment>

<file context>
@@ -44,3 +44,10 @@ internal struct EnumArrays: Codable, JSONEncodable {
+
+extension EnumArrays: UnknownCaseCheckable {
+    internal var containsUnknownDefaultOpenApiCase: Bool {
+        if justSymbol == .unknownDefaultOpenApi { return true }
+        return false
+    }
</file context>
Suggested change
if justSymbol == .unknownDefaultOpenApi { return true }
return false
}
if justSymbol == .unknownDefaultOpenApi { return true }
if arrayEnum?.contains(.unknownDefaultOpenApi) == true { return true }
return false
Fix with Cubic

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ internal enum EnumClass: String, Codable, CaseIterable, CaseIterableDefaultsLast
case xyz = "(xyz)"
case unknownDefaultOpenApi = "unknown_default_open_api"
}

extension EnumClass: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@ internal struct EnumTest: Codable, JSONEncodable {
}
}


extension EnumTest: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if enumString == .unknownDefaultOpenApi { return true }
if enumStringRequired == .unknownDefaultOpenApi { return true }
if enumInteger == .unknownDefaultOpenApi { return true }
if enumNumber == .unknownDefaultOpenApi { return true }
if outerEnum == .unknownDefaultOpenApi { return true }
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ internal struct MapTest: Codable, JSONEncodable {
}
}


extension MapTest: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,10 @@ internal struct Order: Codable, JSONEncodable {

@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)
extension Order: Identifiable {}

extension Order: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if status == .unknownDefaultOpenApi { return true }
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ internal enum OuterEnum: String, Codable, CaseIterable, CaseIterableDefaultsLast
case delivered = "delivered"
case unknownDefaultOpenApi = "unknown_default_open_api"
}

extension OuterEnum: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,10 @@ internal struct Pet: Codable, JSONEncodable, Hashable {

@available(iOS 13, tvOS 13, watchOS 6, macOS 10.15, *)
extension Pet: Identifiable {}

extension Pet: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if status == .encodeValue(.unknownDefaultOpenApi) { return true }
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ extension CaseIterableDefaultsLast {
}
}

/// Protocol for types used as oneOf variants, allowing the oneOf decoder to reject
/// a variant that only decoded successfully because CaseIterableDefaultsLast
/// silently accepted an unknown enum value.
protocol UnknownCaseCheckable {
var containsUnknownDefaultOpenApiCase: Bool { get }
}

extension UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool { false }
}

/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`)
/// or not encoded (`.encodeNothing`). Intended for request payloads.
internal enum NullEncodable<Wrapped> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ internal struct EnumArrays: Sendable, Codable {
}
}


extension EnumArrays: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if justSymbol == .unknownDefaultOpenApi { return true }
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

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

P2: UnknownCaseCheckable ignores arrayEnum, so unknown enum values in array_enum won’t be detected and oneOf discrimination can accept a variant based on unknown-enum fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/swift6/resultLibrary/PetstoreClient/Classes/OpenAPIs/Models/EnumArrays.swift, line 47:

<comment>UnknownCaseCheckable ignores arrayEnum, so unknown enum values in array_enum won’t be detected and oneOf discrimination can accept a variant based on unknown-enum fallback.</comment>

<file context>
@@ -41,3 +41,10 @@ internal struct EnumArrays: Sendable, Codable {
+
+extension EnumArrays: UnknownCaseCheckable {
+    internal var containsUnknownDefaultOpenApiCase: Bool {
+        if justSymbol == .unknownDefaultOpenApi { return true }
+        return false
+    }
</file context>
Fix with Cubic

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ internal enum EnumClass: String, Sendable, Codable, CaseIterable, CaseIterableDe
case xyz = "(xyz)"
case unknownDefaultOpenApi = "unknown_default_open_api"
}

extension EnumClass: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@ internal struct EnumTest: Sendable, Codable {
}
}


extension EnumTest: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if enumString == .unknownDefaultOpenApi { return true }
if enumStringRequired == .unknownDefaultOpenApi { return true }
if enumInteger == .unknownDefaultOpenApi { return true }
if enumNumber == .unknownDefaultOpenApi { return true }
if outerEnum == .unknownDefaultOpenApi { return true }
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,9 @@ internal struct MapTest: Sendable, Codable {
}
}


extension MapTest: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ internal struct Order: Sendable, Codable {


extension Order: Identifiable {}

extension Order: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if status == .unknownDefaultOpenApi { return true }
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ internal enum OuterEnum: String, Sendable, Codable, CaseIterable, CaseIterableDe
case delivered = "delivered"
case unknownDefaultOpenApi = "unknown_default_open_api"
}

extension OuterEnum: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
self == .unknownDefaultOpenApi
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,10 @@ internal struct Pet: Sendable, Codable, Hashable {


extension Pet: Identifiable {}

extension Pet: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if status == .encodeValue(.unknownDefaultOpenApi) { return true }
return false
}
}