diff --git a/.changes/704b9a6e-d859-4706-b39f-91052b375834.json b/.changes/704b9a6e-d859-4706-b39f-91052b375834.json new file mode 100644 index 00000000000..e8ad4fe34ec --- /dev/null +++ b/.changes/704b9a6e-d859-4706-b39f-91052b375834.json @@ -0,0 +1,5 @@ +{ + "id": "704b9a6e-d859-4706-b39f-91052b375834", + "type": "misc", + "description": "Add unbound event stream payload deserialization path" +} \ No newline at end of file diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt index bd738251427..8f06b3ffad9 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/eventstream/EventStreamParserGenerator.kt @@ -150,8 +150,14 @@ class EventStreamParserGenerator( } else { if (unbound.isNotEmpty()) { // all remaining members are bound to payload (but not explicitly bound via @eventPayload) - // FIXME - this would require us to generate a "partial" deserializer like we do for where the builder is passed in - TODO("support for unbound event stream members is not implemented") + // generate a payload deserializer specific to the unbound members (note this will be a deserializer + // for the overall event shape but only payload members will be considered for deserialization), + // and then assign each deserialized payload member to the current builder instance + val payloadDeserializeFn = sdg.payloadDeserializer(ctx, member, unbound) + writer.write("val tmp = #T(message.payload)", payloadDeserializeFn) + unbound.forEach { + writer.write("eb.#1L = tmp.#1L", it.defaultName()) + } } } diff --git a/tests/codegen/event-stream/event-stream-model-template.smithy b/tests/codegen/event-stream/event-stream-model-template.smithy index 28b4320fb1b..f3d91364235 100644 --- a/tests/codegen/event-stream/event-stream-model-template.smithy +++ b/tests/codegen/event-stream/event-stream-model-template.smithy @@ -64,6 +64,11 @@ structure MessageWithNoHeaderPayloadTraits { someString: String, } +structure MessageWithUnboundPayloadTraits { + @eventHeader header: String, + unboundString: String, +} + @streaming union TestStream { MessageWithBlob: MessageWithBlob, @@ -73,5 +78,6 @@ union TestStream { MessageWithHeaders: MessageWithHeaders, MessageWithHeaderAndPayload: MessageWithHeaderAndPayload, MessageWithNoHeaderPayloadTraits: MessageWithNoHeaderPayloadTraits, + MessageWithUnboundPayloadTraits: MessageWithUnboundPayloadTraits, SomeError: SomeError, } diff --git a/tests/codegen/event-stream/src/test/kotlin/EventStreamTests.kt b/tests/codegen/event-stream/src/test/kotlin/EventStreamTests.kt index 5ecb45bc9fe..f4ac22622fc 100644 --- a/tests/codegen/event-stream/src/test/kotlin/EventStreamTests.kt +++ b/tests/codegen/event-stream/src/test/kotlin/EventStreamTests.kt @@ -237,4 +237,30 @@ class EventStreamTests { assertIs(deserialized) assertEquals(event, deserialized) } + + @Test + fun testSerializeMessageWithUnboundPayload() = runTest { + val event = TestStream.MessageWithUnboundPayloadTraits( + MessageWithUnboundPayloadTraits { + header = "a korf is a tiger" + unboundString = "a flix is comb" + }, + ) + + val message = serializedMessage(event) + + val headers = message.headers.associate { it.name to it.value } + assertEquals("event", headers[":message-type"]?.expectString()) + assertEquals("MessageWithUnboundPayloadTraits", headers[":event-type"]?.expectString()) + assertEquals("application/json", headers[":content-type"]?.expectString()) + + assertEquals("a korf is a tiger", headers["header"]?.expectString()) + + val expectedBody = """{"unboundString":"a flix is comb"}""" + assertJsonStringsEqual(expectedBody, message.payload.decodeToString()) + + val deserialized = deserializedEvent(message) + assertIs(deserialized) + assertEquals(event, deserialized) + } }