Skip to content

Commit f169b9f

Browse files
authored
feat: enum and int enums as event headers (#1443)
1 parent 408bed0 commit f169b9f

File tree

7 files changed

+134
-7
lines changed

7 files changed

+134
-7
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "08deb04f-bc93-4d3c-aa2f-d46b2dcce775",
3+
"type": "feature",
4+
"description": "Support enums and int enums as event headers"
5+
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamParserGenerator.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class EventStreamParserGenerator(
147147
val target = ctx.model.expectShape(hdrBinding.target)
148148
val targetSymbol = ctx.symbolProvider.toSymbol(target)
149149

150-
// :test(boolean, byte, short, integer, long, blob, string, timestamp))
150+
// :test(boolean, byte, short, integer, long, blob, string, timestamp, enum, int enum))
151151
val conversionFn = when (target.type) {
152152
ShapeType.BOOLEAN -> RuntimeTypes.AwsEventStream.expectBool
153153
ShapeType.BYTE -> RuntimeTypes.AwsEventStream.expectByte
@@ -157,6 +157,8 @@ class EventStreamParserGenerator(
157157
ShapeType.BLOB -> RuntimeTypes.AwsEventStream.expectByteArray
158158
ShapeType.STRING -> RuntimeTypes.AwsEventStream.expectString
159159
ShapeType.TIMESTAMP -> RuntimeTypes.AwsEventStream.expectTimestamp
160+
ShapeType.ENUM -> RuntimeTypes.AwsEventStream.expectEnumValue
161+
ShapeType.INT_ENUM -> RuntimeTypes.AwsEventStream.expectIntEnumValue
160162
else -> throw CodegenException("unsupported eventHeader shape: member=$hdrBinding; targetShape=$target")
161163
}
162164

@@ -165,7 +167,24 @@ class EventStreamParserGenerator(
165167
} else {
166168
""
167169
}
168-
writer.write("eb.#L = message.headers.find { it.name == #S }?.value?.#T()$defaultValuePostfix", hdrBinding.defaultName(), hdrBinding.memberName, conversionFn)
170+
171+
when (target.type) {
172+
ShapeType.ENUM, ShapeType.INT_ENUM ->
173+
writer.write(
174+
"eb.#L = message.headers.find { it.name == #S }?.value?.#T(#T::fromValue)$defaultValuePostfix",
175+
hdrBinding.defaultName(),
176+
hdrBinding.memberName,
177+
conversionFn,
178+
targetSymbol,
179+
)
180+
else ->
181+
writer.write(
182+
"eb.#L = message.headers.find { it.name == #S }?.value?.#T()$defaultValuePostfix",
183+
hdrBinding.defaultName(),
184+
hdrBinding.memberName,
185+
conversionFn,
186+
)
187+
}
169188
}
170189

171190
if (eventPayloadBinding != null) {

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/eventstream/EventStreamSerializerGenerator.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,15 @@ class EventStreamSerializerGenerator(
183183
ShapeType.BLOB -> "ByteArray"
184184
ShapeType.STRING -> "String"
185185
ShapeType.TIMESTAMP -> "Timestamp"
186+
ShapeType.ENUM -> "String"
187+
ShapeType.INT_ENUM -> "Int32"
186188
else -> throw CodegenException("unsupported shape type `${target.type}` for eventHeader member `$member`; target: $target")
187189
}
188-
val conversion = if (target.type == ShapeType.BYTE) ".toUByte()" else ""
190+
val conversion = when (target.type) {
191+
ShapeType.BYTE -> ".toUByte()"
192+
ShapeType.ENUM, ShapeType.INT_ENUM -> ".value"
193+
else -> ""
194+
}
189195

190196
writer.write(
191197
"input.value.#L?.let { addHeader(#S, #T.#L(it$conversion)) }",

codegen/smithy-aws-kotlin-codegen/src/test/kotlin/software/amazon/smithy/kotlin/codegen/aws/protocols/core/AwsHttpBindingProtocolGeneratorTest.kt

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package software.amazon.smithy.kotlin.codegen.aws.protocols.core
66

77
import software.amazon.smithy.codegen.core.Symbol
8+
import software.amazon.smithy.kotlin.codegen.aws.protocols.json.AwsJsonHttpBindingResolver
89
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
910
import software.amazon.smithy.kotlin.codegen.model.expectShape
1011
import software.amazon.smithy.kotlin.codegen.rendering.protocol.HttpBindingResolver
@@ -70,7 +71,92 @@ class AwsHttpBindingProtocolGeneratorTest {
7071
actual.shouldContainOnlyOnceWithDiff(expected)
7172
}
7273

73-
// A concrete implementation of AwsHttpBindingProtocolGenerator to exercise renderThrowOperationError()
74+
@Test
75+
fun enumEventStreamHeader() {
76+
val model = """
77+
${"$"}version: "2"
78+
79+
namespace com.test
80+
81+
use aws.protocols#restJson1
82+
83+
@restJson1
84+
service Test {
85+
version: "1.0.0",
86+
operations: [TestStreamOp]
87+
}
88+
89+
@http(method: "POST", uri: "/test-eventstream", code: 200)
90+
operation TestStreamOp {
91+
input: TestStreamInputOutput,
92+
output: TestStreamInputOutput,
93+
}
94+
95+
structure TestStreamInputOutput {
96+
@httpPayload
97+
@required
98+
value: TestStream
99+
}
100+
101+
@streaming
102+
union TestStream {
103+
MessageWithHeaders: MessageWithHeaders,
104+
}
105+
106+
structure MessageWithHeaders {
107+
@eventHeader enum: Enum,
108+
@eventHeader intEnum: IntEnum,
109+
}
110+
111+
enum Enum {
112+
DIAMOND
113+
CLUB
114+
HEART
115+
SPADE
116+
}
117+
118+
intEnum IntEnum {
119+
JACK = 1
120+
QUEEN = 2
121+
KING = 3
122+
ACE = 4
123+
JOKER = 5
124+
}
125+
""".toSmithyModel()
126+
val testCtx = model.newTestContext()
127+
val protocolGenerator = TestableAwsHttpBindingProtocolGenerator()
128+
val op = model.expectShape<OperationShape>("com.test#TestStreamOp")
129+
130+
val serializer = protocolGenerator.eventStreamRequestHandler(testCtx.generationCtx, op)
131+
testCtx.generationCtx.delegator.useFileWriter("TestStreamOpOperationSerializer.kt", "com.test.serde") {
132+
it.write("#T(context, request)", serializer)
133+
}
134+
val deserializer = protocolGenerator.eventStreamResponseHandler(testCtx.generationCtx, op)
135+
testCtx.generationCtx.delegator.useFileWriter("TestStreamOpOperationDeserializer.kt", "com.test.serde") {
136+
it.write("#T(context, response)", deserializer)
137+
}
138+
139+
testCtx.generationCtx.delegator.finalize()
140+
testCtx.generationCtx.delegator.flushWriters()
141+
142+
val expectedEnumSerializer = """input.value.enum?.let { addHeader("enum", HeaderValue.String(it.value)) }"""
143+
val expectedIntEnumSerializer = """input.value.intEnum?.let { addHeader("intEnum", HeaderValue.Int32(it.value)) }"""
144+
val actualSerializer = testCtx.manifest.expectFileString("src/main/kotlin/com/test/serde/TestStreamOpOperationSerializer.kt")
145+
actualSerializer.shouldContainOnlyOnceWithDiff(expectedEnumSerializer)
146+
actualSerializer.shouldContainOnlyOnceWithDiff(expectedIntEnumSerializer)
147+
148+
val expectedEnumDeserializer = """eb.enum = message.headers.find { it.name == "enum" }?.value?.expectEnumValue(Enum::fromValue)"""
149+
val expectedIntEnumDeserializer = """eb.intEnum = message.headers.find { it.name == "intEnum" }?.value?.expectIntEnumValue(IntEnum::fromValue)"""
150+
val actualDeserializer = testCtx.manifest.expectFileString("src/main/kotlin/com/test/serde/TestStreamOpOperationDeserializer.kt")
151+
actualDeserializer.shouldContainOnlyOnceWithDiff(expectedEnumDeserializer)
152+
actualDeserializer.shouldContainOnlyOnceWithDiff(expectedIntEnumDeserializer)
153+
}
154+
155+
/**
156+
* A concrete implementation of AwsHttpBindingProtocolGenerator to exercise:
157+
* renderThrowOperationError()
158+
* getProtocolHttpBindingResolver()
159+
*/
74160
class TestableAwsHttpBindingProtocolGenerator : AwsHttpBindingProtocolGenerator() {
75161
override fun renderDeserializeErrorDetails(
76162
ctx: ProtocolGenerator.GenerationContext,
@@ -83,9 +169,8 @@ class AwsHttpBindingProtocolGeneratorTest {
83169
override val defaultTimestampFormat: TimestampFormatTrait.Format
84170
get() = error("Unneeded for test")
85171

86-
override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver {
87-
error("Unneeded for test")
88-
}
172+
override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver =
173+
AwsJsonHttpBindingResolver(model, serviceShape, "application/x-amz-json-1.0")
89174

90175
override fun structuredDataParser(ctx: ProtocolGenerator.GenerationContext): StructuredDataParserGenerator =
91176
object : StructuredDataParserGenerator {

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,8 @@ object RuntimeTypes {
490490
val expectInt64 = symbol("expectInt64")
491491
val expectTimestamp = symbol("expectTimestamp")
492492
val expectString = symbol("expectString")
493+
val expectEnumValue = symbol("expectEnumValue")
494+
val expectIntEnumValue = symbol("expectIntEnumValue")
493495

494496
val sign = symbol("sign")
495497
}

runtime/protocol/aws-event-stream/api/aws-event-stream.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,11 @@ public final class aws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue
147147
public static final fun expectBool (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)Z
148148
public static final fun expectByte (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)B
149149
public static final fun expectByteArray (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)[B
150+
public static final fun expectEnumValue (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
150151
public static final fun expectInt16 (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)S
151152
public static final fun expectInt32 (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)I
152153
public static final fun expectInt64 (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)J
154+
public static final fun expectIntEnumValue (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
153155
public static final fun expectString (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)Ljava/lang/String;
154156
public static final fun expectTimestamp (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)Laws/smithy/kotlin/runtime/time/Instant;
155157
public static final fun expectUuid (Laws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue;)Laws/smithy/kotlin/runtime/util/Uuid;

runtime/protocol/aws-event-stream/common/src/aws/smithy/kotlin/runtime/awsprotocol/eventstream/HeaderValue.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,11 @@ public fun HeaderValue.expectTimestamp(): Instant = checkNotNull((this as? Heade
190190

191191
@InternalApi
192192
public fun HeaderValue.expectUuid(): Uuid = checkNotNull((this as? HeaderValue.Uuid)?.value) { "expected HeaderValue.Bool, found: $this" }
193+
194+
@InternalApi
195+
public fun <T> HeaderValue.expectEnumValue(fromValue: (String) -> T): T =
196+
fromValue(expectString())
197+
198+
@InternalApi
199+
public fun <T> HeaderValue.expectIntEnumValue(fromValue: (Int) -> T): T =
200+
fromValue(expectInt32())

0 commit comments

Comments
 (0)