Skip to content

Commit 3d62c06

Browse files
committed
Skip duplicate enum values
1 parent 8cbeb8b commit 3d62c06

File tree

2 files changed

+80
-20
lines changed

2 files changed

+80
-20
lines changed
Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ enum RawEnumBackingType {
2323
case integer
2424
}
2525

26+
/// The extracted enum value.
27+
private enum EnumValue: Hashable, CustomStringConvertible {
28+
29+
/// A string value.
30+
case string(String)
31+
32+
/// An integer value.
33+
case integer(Int)
34+
35+
var description: String {
36+
switch self {
37+
case .string(let value): return "\"\(value)\""
38+
case .integer(let value): return String(value)
39+
}
40+
}
41+
}
42+
2643
extension FileTranslator {
2744

2845
/// Returns a declaration of the specified raw value-based enum schema.
@@ -42,32 +59,48 @@ extension FileTranslator {
4259
isNullable: Bool,
4360
allowedValues: [AnyCodable]
4461
) throws -> Declaration {
45-
let cases: [(String, LiteralDescription)] = try allowedValues.map(\.value)
46-
.map { anyValue in
47-
switch backingType {
48-
case .string:
49-
// In nullable enum schemas, empty strings are parsed as Void.
50-
// This is unlikely to be fixed, so handling that case here.
51-
// https://github.com/apple/swift-openapi-generator/issues/118
52-
if isNullable && anyValue is Void { return (context.asSwiftSafeName(""), .string("")) }
62+
var seen: Set<EnumValue> = []
63+
var cases: [(String, LiteralDescription)] = []
64+
func shouldAdd(_ value: EnumValue) throws -> Bool {
65+
guard seen.insert(value).inserted else {
66+
try diagnostics.emit(
67+
.warning(
68+
message: "Duplicate enum value, skipping",
69+
context: ["value": "\(value)", "foundIn": typeName.description]
70+
)
71+
)
72+
return false
73+
}
74+
return true
75+
}
76+
for anyValue in allowedValues.map(\.value) {
77+
switch backingType {
78+
case .string:
79+
// In nullable enum schemas, empty strings are parsed as Void.
80+
// This is unlikely to be fixed, so handling that case here.
81+
// https://github.com/apple/swift-openapi-generator/issues/118
82+
if isNullable && anyValue is Void {
83+
if try shouldAdd(.string("")) { cases.append((context.asSwiftSafeName(""), .string(""))) }
84+
} else {
5385
guard let rawValue = anyValue as? String else {
5486
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
5587
}
5688
let caseName = context.asSwiftSafeName(rawValue)
57-
return (caseName, .string(rawValue))
58-
case .integer:
59-
let rawValue: Int
60-
if let intRawValue = anyValue as? Int {
61-
rawValue = intRawValue
62-
} else if let stringRawValue = anyValue as? String, let intRawValue = Int(stringRawValue) {
63-
rawValue = intRawValue
64-
} else {
65-
throw GenericError(message: "Disallowed value for an integer enum '\(typeName)': \(anyValue)")
66-
}
67-
let caseName = rawValue < 0 ? "_n\(abs(rawValue))" : "_\(rawValue)"
68-
return (caseName, .int(rawValue))
89+
if try shouldAdd(.string(rawValue)) { cases.append((caseName, .string(rawValue))) }
90+
}
91+
case .integer:
92+
let rawValue: Int
93+
if let intRawValue = anyValue as? Int {
94+
rawValue = intRawValue
95+
} else if let stringRawValue = anyValue as? String, let intRawValue = Int(stringRawValue) {
96+
rawValue = intRawValue
97+
} else {
98+
throw GenericError(message: "Disallowed value for an integer enum '\(typeName)': \(anyValue)")
6999
}
100+
let caseName = rawValue < 0 ? "_n\(abs(rawValue))" : "_\(rawValue)"
101+
if try shouldAdd(.integer(rawValue)) { cases.append((caseName, .int(rawValue))) }
70102
}
103+
}
71104
let baseConformance: String
72105
switch backingType {
73106
case .string: baseConformance = Constants.RawEnum.baseConformanceString

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,33 @@ final class SnippetBasedReferenceTests: XCTestCase {
13101310
)
13111311
}
13121312

1313+
func testComponentsSchemasStringEnumWithDuplicates() throws {
1314+
try self.assertSchemasTranslation(
1315+
ignoredDiagnosticMessages: ["Duplicate enum value, skipping"],
1316+
"""
1317+
schemas:
1318+
MyEnum:
1319+
type: string
1320+
enum:
1321+
- one
1322+
- two
1323+
- three
1324+
- two
1325+
- four
1326+
""",
1327+
"""
1328+
public enum Schemas {
1329+
@frozen public enum MyEnum: String, Codable, Hashable, Sendable, CaseIterable {
1330+
case one = "one"
1331+
case two = "two"
1332+
case three = "three"
1333+
case four = "four"
1334+
}
1335+
}
1336+
"""
1337+
)
1338+
}
1339+
13131340
func testComponentsSchemasIntEnum() throws {
13141341
try self.assertSchemasTranslation(
13151342
"""

0 commit comments

Comments
 (0)