Skip to content

Commit e7cac50

Browse files
authored
Support integer-backed enums (#242)
1 parent 074b839 commit e7cac50

File tree

13 files changed

+110
-31
lines changed

13 files changed

+110
-31
lines changed

Sources/_OpenAPIGeneratorCore/Layers/StructuredSwiftRepresentation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ enum EnumCaseKind: Equatable, Codable {
544544
/// A case with a name and a raw value.
545545
///
546546
/// For example: `case foo = "Foo"`.
547-
case nameWithRawValue(String)
547+
case nameWithRawValue(LiteralDescription)
548548

549549
/// A case with a name and associated values.
550550
///

Sources/_OpenAPIGeneratorCore/Renderer/TextBasedRenderer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ struct TextBasedRenderer: RendererProtocol {
520520
case .nameOnly:
521521
return ""
522522
case .nameWithRawValue(let rawValue):
523-
return " = \"\(rawValue)\""
523+
return " = \(renderedLiteral(rawValue))"
524524
case .nameWithAssociatedValues(let values):
525525
if values.isEmpty {
526526
return ""

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ extension FileTranslator {
220220
members: [
221221
.enumCase(
222222
name: swiftName,
223-
kind: swiftName == originalName ? .nameOnly : .nameWithRawValue(originalName)
223+
kind: swiftName == originalName ? .nameOnly : .nameWithRawValue(.string(originalName))
224224
)
225225
]
226226
)

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawRepresentableEnum.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension FileTranslator {
3131
typeName: TypeName,
3232
conformances: [String],
3333
userDescription: String?,
34-
cases: [(caseName: String, rawValue: String)],
34+
cases: [(caseName: String, rawExpr: LiteralDescription)],
3535
unknownCaseName: String?,
3636
unknownCaseDescription: String?,
3737
customSwitchedExpression: (Expression) -> Expression = { $0 }
@@ -40,10 +40,10 @@ extension FileTranslator {
4040
let generateUnknownCases = unknownCaseName != nil
4141
let knownCases: [Declaration] =
4242
cases
43-
.map { caseName, rawValue in
43+
.map { caseName, rawExpr in
4444
.enumCase(
4545
name: caseName,
46-
kind: generateUnknownCases ? .nameOnly : .nameWithRawValue(rawValue)
46+
kind: generateUnknownCases ? .nameOnly : .nameWithRawValue(rawExpr)
4747
)
4848
}
4949

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateSchema.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,20 @@ extension FileTranslator {
112112
guard let allowedValues = coreContext.allowedValues else {
113113
throw GenericError(message: "Unexpected non-global string for \(typeName)")
114114
}
115-
let enumDecl = try translateStringEnum(
115+
let enumDecl = try translateRawEnum(
116+
backingType: .string,
117+
typeName: typeName,
118+
userDescription: overrides.userDescription ?? coreContext.description,
119+
isNullable: coreContext.nullable,
120+
allowedValues: allowedValues
121+
)
122+
return [enumDecl]
123+
case let .integer(coreContext, _):
124+
guard let allowedValues = coreContext.allowedValues else {
125+
throw GenericError(message: "Unexpected non-global integer for \(typeName)")
126+
}
127+
let enumDecl = try translateRawEnum(
128+
backingType: .integer,
116129
typeName: typeName,
117130
userDescription: overrides.userDescription ?? coreContext.description,
118131
isNullable: coreContext.nullable,

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,66 @@
1313
//===----------------------------------------------------------------------===//
1414
import OpenAPIKit
1515

16+
/// The backing type of a raw enum.
17+
enum RawEnumBackingType {
18+
19+
/// Backed by a `String`.
20+
case string
21+
22+
/// Backed by an `Int`.
23+
case integer
24+
}
25+
1626
extension FileTranslator {
1727

18-
/// Returns a declaration of the specified string-based enum schema.
28+
/// Returns a declaration of the specified raw value-based enum schema.
1929
/// - Parameters:
30+
/// - backingType: The backing type of the enum.
2031
/// - typeName: The name of the type to give to the declared enum.
2132
/// - userDescription: A user-specified description from the OpenAPI
2233
/// document.
2334
/// - isNullable: Whether the enum schema is nullable.
2435
/// - allowedValues: The enumerated allowed values.
25-
func translateStringEnum(
36+
func translateRawEnum(
37+
backingType: RawEnumBackingType,
2638
typeName: TypeName,
2739
userDescription: String?,
2840
isNullable: Bool,
2941
allowedValues: [AnyCodable]
3042
) throws -> Declaration {
31-
let rawValues = try allowedValues.map(\.value)
43+
let cases: [(String, LiteralDescription)] =
44+
try allowedValues
45+
.map(\.value)
3246
.map { anyValue in
33-
// In nullable enum schemas, empty strings are parsed as Void.
34-
// This is unlikely to be fixed, so handling that case here.
35-
// https://github.com/apple/swift-openapi-generator/issues/118
36-
if isNullable && anyValue is Void {
37-
return ""
38-
}
39-
guard let string = anyValue as? String else {
40-
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
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 {
53+
return (swiftSafeName(for: ""), .string(""))
54+
}
55+
guard let rawValue = anyValue as? String else {
56+
throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)")
57+
}
58+
let caseName = swiftSafeName(for: rawValue)
59+
return (caseName, .string(rawValue))
60+
case .integer:
61+
guard let rawValue = anyValue as? Int else {
62+
throw GenericError(message: "Disallowed value for an integer enum '\(typeName)': \(anyValue)")
63+
}
64+
let caseName = "_\(rawValue)"
65+
return (caseName, .int(rawValue))
4166
}
42-
return string
4367
}
44-
let cases = rawValues.map { rawValue in
45-
let caseName = swiftSafeName(for: rawValue)
46-
return (caseName, rawValue)
68+
let baseConformance: String
69+
switch backingType {
70+
case .string:
71+
baseConformance = Constants.RawEnum.baseConformanceString
72+
case .integer:
73+
baseConformance = Constants.RawEnum.baseConformanceInteger
4774
}
48-
let conformances = [Constants.StringEnum.baseConformance] + Constants.StringEnum.conformances
75+
let conformances = [baseConformance] + Constants.RawEnum.conformances
4976
return try translateRawRepresentableEnum(
5077
typeName: typeName,
5178
conformances: conformances,

Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStructBlueprint.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ extension FileTranslator {
157157
let rawName = property.originalName
158158
return .enumCase(
159159
name: swiftName,
160-
kind: swiftName == rawName ? .nameOnly : .nameWithRawValue(property.originalName)
160+
kind: swiftName == rawName ? .nameOnly : .nameWithRawValue(.string(property.originalName))
161161
)
162162
}
163163
return .enum(

Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,14 @@ enum Constants {
138138
static let variableName: String = "additionalProperties"
139139
}
140140

141-
/// Constants related to all generated string-based enums.
142-
enum StringEnum {
141+
/// Constants related to all generated raw enums.
142+
enum RawEnum {
143143

144-
/// The name of the base conformance.
145-
static let baseConformance: String = "String"
144+
/// The name of the base conformance for string-based enums.
145+
static let baseConformanceString: String = "String"
146+
147+
/// The name of the base conformance for int-based enums.
148+
static let baseConformanceInteger: String = "Int"
146149

147150
/// The types that every enum conforms to.
148151
static let conformances: [String] = [

Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ struct TypeMatcher {
202202
typeName = .swift("Double")
203203
}
204204
case .integer(let core, _):
205+
if core.allowedValues != nil {
206+
// custom enum isn't a builtin
207+
return nil
208+
}
205209
switch core.format {
206210
case .int32:
207211
typeName = .swift("Int32")

Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,10 @@ extension TypesFileTranslator {
210210
guard !contentTypes.isEmpty else {
211211
return nil
212212
}
213-
let cases: [(caseName: String, rawValue: String)] =
213+
let cases: [(caseName: String, rawExpr: LiteralDescription)] =
214214
contentTypes
215215
.map { contentType in
216-
(contentSwiftName(contentType), contentType.lowercasedTypeAndSubtype)
216+
(contentSwiftName(contentType), .string(contentType.lowercasedTypeAndSubtype))
217217
}
218218
return try translateRawRepresentableEnum(
219219
typeName: acceptableContentTypeName,

0 commit comments

Comments
 (0)