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,60 @@ 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}}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{#isArray}}
{{#required}}{{^isNullable}}
if {{{name}}}.contains(.unknownDefaultOpenApi) { return true }
{{/isNullable}}{{#isNullable}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/isNullable}}{{/required}}
{{^required}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/required}}
{{/isArray}}
{{/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}}
{{#isArray}}
{{#required}}{{^isNullable}}
if {{{name}}}.contains(.unknownDefaultOpenApi) { return true }
{{/isNullable}}{{#isNullable}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/isNullable}}{{/required}}
{{^required}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/required}}
{{/isArray}}
{{/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,60 @@ 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}}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
if {{{name}}} == .encodeValue(.unknownDefaultOpenApi) { return true }
{{/vendorExtensions.x-null-encodable}}
{{^vendorExtensions.x-null-encodable}}
if {{{name}}} == .unknownDefaultOpenApi { return true }
{{/vendorExtensions.x-null-encodable}}
{{/isContainer}}
{{#isArray}}
{{#required}}{{^isNullable}}
if {{{name}}}.contains(.unknownDefaultOpenApi) { return true }
{{/isNullable}}{{#isNullable}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/isNullable}}{{/required}}
{{^required}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/required}}
{{/isArray}}
{{/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}}
{{#isArray}}
{{#required}}{{^isNullable}}
if {{{name}}}.contains(.unknownDefaultOpenApi) { return true }
{{/isNullable}}{{#isNullable}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/isNullable}}{{/required}}
{{^required}}
if {{{name}}}?.contains(.unknownDefaultOpenApi) == true { return true }
{{/required}}
{{/isArray}}
{{/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,12 @@ internal struct EnumArrays: Codable, JSONEncodable {
}
}


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

if arrayEnum?.contains(.unknownDefaultOpenApi) == true { return true }
return false
}
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
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,12 @@ internal struct EnumArrays: Sendable, Codable {
}
}


extension EnumArrays: UnknownCaseCheckable {
internal var containsUnknownDefaultOpenApiCase: Bool {
if justSymbol == .unknownDefaultOpenApi { return true }
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

if arrayEnum?.contains(.unknownDefaultOpenApi) == true { return true }
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
}
}
Loading