Skip to content

Commit 35d41e8

Browse files
author
Ed Paulosky
authored
feat: Adds IntEnum support (#484)
* feat: Adds IntEnum support - Generate IntEnum shapes - Moves shared enum casing logic to Utils so it can be easily shared - Updates test generator to sort members by lower camel case * Adds IntEnumGenerator * ktlintformat * Addresses swiftlint violations * Adds proper import and removes wildcard import
1 parent 749c379 commit 35d41e8

File tree

12 files changed

+274
-23
lines changed

12 files changed

+274
-23
lines changed

Packages/ClientRuntime/Sources/Middleware/OrderedGroup.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public struct OrderedGroup<Input, Output, Context: MiddlewareContext> {
8282
order.insert(relativeTo: relativeTo, position: position, ids: middleware.id)
8383
}
8484

85-
func get(id: String)-> AnyMiddleware<Input, Output, Context>? {
85+
func get(id: String) -> AnyMiddleware<Input, Output, Context>? {
8686
return _items[id]
8787
}
8888
}

Packages/ClientRuntime/Sources/Util/PlatformOperationSystemVersion.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// SPDX-License-Identifier: Apache-2.0
66
//
77

8-
#if os(iOS) || os (watchOS) || os(macOS) || os(tvOS)
8+
#if os(iOS) || os(watchOS) || os(macOS) || os(tvOS)
99
import Foundation.NSProcessInfo
1010

1111
public struct PlatformOperationSystemVersion {

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/CodegenVisitor.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import software.amazon.smithy.codegen.core.SymbolProvider
1111
import software.amazon.smithy.model.Model
1212
import software.amazon.smithy.model.knowledge.ServiceIndex
1313
import software.amazon.smithy.model.neighbor.Walker
14+
import software.amazon.smithy.model.shapes.IntegerShape
1415
import software.amazon.smithy.model.shapes.ServiceShape
1516
import software.amazon.smithy.model.shapes.Shape
1617
import software.amazon.smithy.model.shapes.ShapeVisitor
@@ -167,6 +168,13 @@ class CodegenVisitor(context: PluginContext) : ShapeVisitor.Default<Void>() {
167168
return null
168169
}
169170

171+
override fun integerShape(shape: IntegerShape): Void? {
172+
if (shape.isIntEnumShape()) {
173+
writers.useShapeWriter(shape) { writer: SwiftWriter -> IntEnumGenerator(model, symbolProvider, writer, shape.asIntEnumShape().get(), settings).render() }
174+
}
175+
return null
176+
}
177+
170178
override fun unionShape(shape: UnionShape): Void? {
171179
writers.useShapeWriter(shape) { writer: SwiftWriter -> UnionGenerator(model, symbolProvider, writer, shape, settings).render() }
172180
return null

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@ import software.amazon.smithy.model.shapes.StringShape
1212
import software.amazon.smithy.model.traits.EnumDefinition
1313
import software.amazon.smithy.model.traits.EnumTrait
1414
import software.amazon.smithy.swift.codegen.customtraits.NestedTrait
15-
import software.amazon.smithy.swift.codegen.lang.reservedWords
1615
import software.amazon.smithy.swift.codegen.model.expectShape
1716
import software.amazon.smithy.swift.codegen.model.hasTrait
1817
import software.amazon.smithy.swift.codegen.model.nestedNamespaceType
19-
import software.amazon.smithy.utils.CaseUtils
2018

2119
/**
2220
* Generates an appropriate Swift type for a Smithy enum string.
@@ -227,19 +225,6 @@ class EnumGenerator(
227225
* them to camelCase after removing chars except alphanumeric, space and underscore.
228226
*/
229227
fun EnumDefinition.swiftEnumCaseName(shouldBeEscaped: Boolean = true): String {
230-
var enumCaseName = CaseUtils.toCamelCase(
231-
name.orElseGet {
232-
value
233-
}.replace(Regex("[^a-zA-Z0-9_ ]"), "")
234-
)
235-
if (!SymbolVisitor.isValidSwiftIdentifier(enumCaseName)) {
236-
enumCaseName = "_$enumCaseName"
237-
}
238-
239-
if (shouldBeEscaped && reservedWords.contains(enumCaseName)) {
240-
enumCaseName = SymbolVisitor.escapeReservedWords(enumCaseName)
241-
}
242-
243-
return enumCaseName
228+
return swiftEnumCaseName(name, value, shouldBeEscaped)
244229
}
245230
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package software.amazon.smithy.swift.codegen
2+
3+
import software.amazon.smithy.codegen.core.SymbolProvider
4+
import software.amazon.smithy.model.Model
5+
import software.amazon.smithy.model.shapes.IntEnumShape
6+
import software.amazon.smithy.model.shapes.MemberShape
7+
import software.amazon.smithy.model.shapes.ServiceShape
8+
import software.amazon.smithy.model.traits.EnumValueTrait
9+
import software.amazon.smithy.swift.codegen.customtraits.NestedTrait
10+
import software.amazon.smithy.swift.codegen.model.expectShape
11+
import software.amazon.smithy.swift.codegen.model.hasTrait
12+
import software.amazon.smithy.swift.codegen.model.nestedNamespaceType
13+
import java.util.Optional
14+
15+
class IntEnumGenerator(
16+
private val model: Model,
17+
private val symbolProvider: SymbolProvider,
18+
private val writer: SwiftWriter,
19+
private val shape: IntEnumShape,
20+
private val settings: SwiftSettings
21+
) {
22+
private var allCasesBuilder: MutableList<String> = mutableListOf()
23+
private var rawValuesBuilder: MutableList<String> = mutableListOf()
24+
25+
fun render() {
26+
val symbol = symbolProvider.toSymbol(shape)
27+
writer.putContext("enum.name", symbol.name)
28+
val isNestedType = shape.hasTrait<NestedTrait>()
29+
if (isNestedType) {
30+
val service = model.expectShape<ServiceShape>(settings.service)
31+
writer.openBlock("extension ${service.nestedNamespaceType(symbolProvider)} {", "}") {
32+
renderEnum()
33+
}
34+
} else {
35+
renderEnum()
36+
}
37+
writer.removeContext("enum.name")
38+
}
39+
40+
private fun renderEnum() {
41+
writer.writeShapeDocs(shape)
42+
writer.writeAvailableAttribute(null, shape)
43+
writer.openBlock("public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {", "}", SwiftTypes.Protocols.Equatable, SwiftTypes.Protocols.RawRepresentable, SwiftTypes.Protocols.CaseIterable, SwiftTypes.Protocols.Codable, SwiftTypes.Protocols.Hashable) {
44+
createEnumWriterContexts()
45+
// add the sdkUnknown case which will always be last
46+
writer.write("case sdkUnknown(\$N)", SwiftTypes.Int)
47+
48+
writer.write("")
49+
50+
// Generate allCases static array
51+
generateAllCasesBlock()
52+
53+
// Generate initializer from rawValue
54+
generateInitFromRawValueBlock()
55+
56+
// Generate rawValue internal enum
57+
generateRawValueEnumBlock()
58+
}
59+
}
60+
61+
fun addEnumCaseToEnum(caseShape: MemberShape) {
62+
writer.writeMemberDocs(model, caseShape)
63+
writer.write("case ${caseShape.swiftEnumCaseName()}")
64+
}
65+
66+
fun addEnumCaseToAllCases(caseShape: MemberShape) {
67+
allCasesBuilder.add(".${caseShape.swiftEnumCaseName(false)}")
68+
}
69+
70+
fun addEnumCaseToRawValuesEnum(caseShape: MemberShape) {
71+
rawValuesBuilder.add("case .${caseShape.swiftEnumCaseName(false)}: return ${caseShape.swiftEnumCaseValue()}")
72+
}
73+
74+
fun createEnumWriterContexts() {
75+
shape
76+
.getCaseMembers()
77+
.sortedBy { it.memberName }
78+
.forEach {
79+
// Add all given enum cases to generated enum definition
80+
addEnumCaseToEnum(it)
81+
addEnumCaseToAllCases(it)
82+
addEnumCaseToRawValuesEnum(it)
83+
}
84+
}
85+
86+
fun generateAllCasesBlock() {
87+
allCasesBuilder.add(".sdkUnknown(0)")
88+
writer.openBlock("public static var allCases: [\$enum.name:L] {", "}") {
89+
writer.openBlock("return [", "]") {
90+
writer.write(allCasesBuilder.joinToString(",\n"))
91+
}
92+
}
93+
}
94+
95+
fun generateInitFromRawValueBlock() {
96+
writer.openBlock("public init(rawValue: \$N) {", "}", SwiftTypes.Int) {
97+
writer.write("let value = Self.allCases.first(where: { \$\$0.rawValue == rawValue })")
98+
writer.write("self = value ?? Self.sdkUnknown(rawValue)")
99+
}
100+
}
101+
102+
fun generateRawValueEnumBlock() {
103+
rawValuesBuilder.add("case let .sdkUnknown(s): return s")
104+
writer.openBlock("public var rawValue: \$N {", "}", SwiftTypes.Int) {
105+
writer.write("switch self {")
106+
writer.write(rawValuesBuilder.joinToString("\n"))
107+
writer.write("}")
108+
}
109+
}
110+
111+
fun IntEnumShape.getCaseMembers(): List<MemberShape> {
112+
return members().filter {
113+
it.hasTrait<EnumValueTrait>()
114+
}
115+
}
116+
117+
fun MemberShape.swiftEnumCaseName(shouldBeEscaped: Boolean = true): String {
118+
return swiftEnumCaseName(
119+
Optional.of(memberName),
120+
"${swiftEnumCaseValue()}",
121+
shouldBeEscaped
122+
)
123+
}
124+
125+
fun MemberShape.swiftEnumCaseValue(): Int {
126+
return expectTrait<EnumValueTrait>(EnumValueTrait::class.java).expectIntValue()
127+
}
128+
}

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import software.amazon.smithy.swift.codegen.customtraits.SwiftBoxTrait
2929
import software.amazon.smithy.swift.codegen.model.hasTrait
3030
import software.amazon.smithy.swift.codegen.model.recursiveSymbol
3131
import software.amazon.smithy.swift.codegen.model.toMemberNames
32+
import software.amazon.smithy.swift.codegen.utils.toLowerCamelCase
3233
import software.amazon.smithy.utils.StringUtils.lowerCase
3334

3435
/**
@@ -228,7 +229,7 @@ class ShapeValueGenerator(
228229
// this is important because when a struct is generated in swift it is generated with its members sorted by name.
229230
// when you instantiate that struct you have to call params in order with their param names. if you don't it won't compile
230231
// so we sort here before we write any of the members with their values
231-
val sortedMembers = node.members.toSortedMap(compareBy<StringNode> { it.value.lowercase() })
232+
val sortedMembers = node.members.toSortedMap(compareBy<StringNode> { it.value.toLowerCamelCase() })
232233
sortedMembers.forEach { (keyNode, valueNode) ->
233234
val memberShape: Shape
234235
when (currShape) {
@@ -324,6 +325,14 @@ class ShapeValueGenerator(
324325
writer.writeInline("Date(timeIntervalSince1970: \$L)", node.value)
325326
}
326327

328+
ShapeType.INT_ENUM -> {
329+
val enumSymbol = generator.symbolProvider.toSymbol(currShape)
330+
writer.writeInline(
331+
"\$L(rawValue: \$L)",
332+
enumSymbol, node.value
333+
)
334+
}
335+
327336
ShapeType.BYTE, ShapeType.SHORT, ShapeType.INTEGER,
328337
ShapeType.LONG, ShapeType.DOUBLE, ShapeType.FLOAT -> writer.writeInline("\$L", node.value)
329338

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SymbolVisitor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,9 @@ class SymbolVisitor(private val model: Model, swiftSettings: SwiftSettings) :
242242
}
243243

244244
private fun numberShape(shape: Shape?, typeName: String, defaultValue: String = "0"): Symbol {
245+
if (shape != null && shape.isIntEnumShape()) {
246+
return createEnumSymbol(shape)
247+
}
245248
return createSymbolBuilder(shape, typeName, "Swift").putProperty(SymbolProperty.DEFAULT_VALUE_KEY, defaultValue).build()
246249
}
247250

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/Utils.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,32 @@
44
*/
55

66
package software.amazon.smithy.swift.codegen
7+
import software.amazon.smithy.swift.codegen.lang.reservedWords
8+
import software.amazon.smithy.utils.CaseUtils
79
import java.util.Optional
810

911
fun <T> Optional<T>.getOrNull(): T? = if (isPresent) get() else null
1012

1113
fun String.removeSurroundingBackticks() = removeSurrounding("`", "`")
14+
15+
/**
16+
* Creates an idiomatic name for swift enum cases given an optional name and value.
17+
* Uses either name or value attributes of EnumDefinition in that order and formats
18+
* them to camelCase after removing chars except alphanumeric, space and underscore.
19+
*/
20+
fun swiftEnumCaseName(name: Optional<String>, value: String, shouldBeEscaped: Boolean = true): String {
21+
var enumCaseName = CaseUtils.toCamelCase(
22+
name
23+
.orElseGet { value }
24+
.replace(Regex("[^a-zA-Z0-9_ ]"), "")
25+
)
26+
if (!SymbolVisitor.isValidSwiftIdentifier(enumCaseName)) {
27+
enumCaseName = "_$enumCaseName"
28+
}
29+
30+
if (shouldBeEscaped && reservedWords.contains(enumCaseName)) {
31+
enumCaseName = SymbolVisitor.escapeReservedWords(enumCaseName)
32+
}
33+
34+
return enumCaseName
35+
}

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpBindingProtocolGenerator.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import software.amazon.smithy.model.neighbor.RelationshipType
1212
import software.amazon.smithy.model.neighbor.Walker
1313
import software.amazon.smithy.model.shapes.BlobShape
1414
import software.amazon.smithy.model.shapes.CollectionShape
15+
import software.amazon.smithy.model.shapes.IntEnumShape
1516
import software.amazon.smithy.model.shapes.MemberShape
1617
import software.amazon.smithy.model.shapes.OperationShape
1718
import software.amazon.smithy.model.shapes.Shape
@@ -103,6 +104,7 @@ fun formatHeaderOrQueryValue(
103104
}
104105
Pair(formattedItemValue, requiresDoCatch)
105106
}
107+
is IntEnumShape -> Pair("$memberName.rawValue", false)
106108
else -> Pair(memberName, false)
107109
}
108110
}

smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HttpResponseHeaders.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,16 @@ class HttpResponseHeaders(
4444
writer.indent()
4545
when (memberTarget) {
4646
is NumberShape -> {
47-
val memberValue = stringToNumber(memberTarget, headerDeclaration, true)
48-
writer.write("self.\$L = $memberValue", memberName)
47+
if (memberTarget.isIntEnumShape) {
48+
val enumSymbol = ctx.symbolProvider.toSymbol(memberTarget)
49+
writer.write(
50+
"self.\$L = \$L(rawValue: \$L(\$L) ?? 0)",
51+
memberName, enumSymbol, SwiftTypes.Int, headerDeclaration
52+
)
53+
} else {
54+
val memberValue = stringToNumber(memberTarget, headerDeclaration, true)
55+
writer.write("self.\$L = \$L", memberName, memberValue)
56+
}
4957
}
5058
is BlobShape -> {
5159
val memberValue = "$headerDeclaration.data(using: .utf8)"
@@ -94,7 +102,14 @@ class HttpResponseHeaders(
94102
invalidHeaderListErrorName = "invalidBooleanHeaderList"
95103
"${SwiftTypes.Bool}(\$0)"
96104
}
97-
is NumberShape -> "${stringToNumber(collectionMemberTarget, "\$0", false)}"
105+
is NumberShape -> {
106+
if (collectionMemberTarget.isIntEnumShape) {
107+
val enumSymbol = ctx.symbolProvider.toSymbol(collectionMemberTarget)
108+
"${SwiftTypes.Int}(\$0).map({ intValue in $enumSymbol(rawValue: intValue) })"
109+
} else {
110+
"${stringToNumber(collectionMemberTarget, "\$0", false)}"
111+
}
112+
}
98113
is TimestampShape -> {
99114
val bindingIndex = HttpBindingIndex.of(ctx.model)
100115
val tsFormat = bindingIndex.determineTimestampFormat(
@@ -133,7 +148,7 @@ class HttpResponseHeaders(
133148
// render map function
134149
val collectionMemberTargetShape = ctx.model.expectShape(memberTarget.member.target)
135150
val collectionMemberTargetSymbol = ctx.symbolProvider.toSymbol(collectionMemberTargetShape)
136-
if (!collectionMemberTargetSymbol.isBoxed()) {
151+
if (!collectionMemberTargetSymbol.isBoxed() || collectionMemberTargetShape.isIntEnumShape()) {
137152
writer.openBlock("self.\$L = try \$LHeaderValues.map {", "}", memberName, memberName) {
138153
val transformedHeaderDeclaration = "${memberName}Transformed"
139154
writer.openBlock("guard let \$L = \$L else {", "}", transformedHeaderDeclaration, conversion) {

0 commit comments

Comments
 (0)