From b2f5023ba3a36010d4ce5ce2a445b85ea73b7af6 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 13 Sep 2023 15:12:39 -0400 Subject: [PATCH 01/18] add initial messages support --- .../HttpResponseTraitWithoutHttpPayload.kt | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index 8cf4024a0..e22762b87 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -49,16 +49,19 @@ class HttpResponseTraitWithoutHttpPayload( val bodyMembersWithoutQueryTrait = bodyMembers .filter { !it.member.hasTrait(HttpQueryTrait::class.java) } .toMutableSet() + val initialResponseMembers = bodyMembers.filter { + val targetShape = it.member.targetOrSelf(ctx.model) + targetShape?.hasTrait(StreamingTrait::class.java) == false + }.toMutableSet() val streamingMember = bodyMembers.firstOrNull { it.member.targetOrSelf(ctx.model).hasTrait(StreamingTrait::class.java) } - if (streamingMember != null) { - writeStreamingMember(streamingMember) + writeStreamingMember(streamingMember, initialResponseMembers) } else if (bodyMembersWithoutQueryTrait.isNotEmpty()) { writeNonStreamingMembers(bodyMembersWithoutQueryTrait) } } - fun writeStreamingMember(streamingMember: HttpBindingDescriptor) { + fun writeStreamingMember(streamingMember: HttpBindingDescriptor, initialResponseMembers: Set) { val shape = ctx.model.expectShape(streamingMember.member.target) val symbol = ctx.symbolProvider.toSymbol(shape) val memberName = ctx.symbolProvider.toMemberName(streamingMember.member) @@ -74,6 +77,7 @@ class HttpResponseTraitWithoutHttpPayload( symbol ) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) + writeInitialResponseMembers(initialResponseMembers) } writer.indent() writer.write("self.\$L = nil", memberName).closeBlock("}") @@ -132,5 +136,35 @@ class HttpResponseTraitWithoutHttpPayload( writer.write("}") } + fun writeInitialResponseMembers(initialResponseMembers: Set) { + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + writer.apply { + write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") + indent() + write("let decoder = JSONDecoder()") + write("do {") + indent() + write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") + dedent() + write("} catch {") + indent() + write("print(\"Error decoding JSON: \\(error)\")") + write("self.$responseMemberName = nil") + dedent() + write("}") + dedent() + write("} else {") + indent() + write("self.$responseMemberName = nil") + dedent() + write("}") + } + } + } + + + private val path: String = "properties.".takeIf { outputShape.hasTrait() } ?: "" } From c72f0cc7e69af2f2365d7298b7b6442f7006f7af Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 14 Sep 2023 09:36:52 -0400 Subject: [PATCH 02/18] remove unnecessary space --- .../bindingTraits/HttpResponseTraitWithoutHttpPayload.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index e22762b87..d284f0910 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -164,7 +164,5 @@ class HttpResponseTraitWithoutHttpPayload( } } - - private val path: String = "properties.".takeIf { outputShape.hasTrait() } ?: "" } From 02c0a834765db1bbcafd86ae424840890a8a0632 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 14 Sep 2023 18:40:58 -0400 Subject: [PATCH 03/18] add tests + general function in helper file --- .../bindingTraits/HttpResponseTraitPayload.kt | 2 +- .../HttpResponseTraitWithHttpPayload.kt | 9 +++++ .../HttpResponseTraitWithoutHttpPayload.kt | 30 +---------------- .../bindingTraits/ResponseHelpers.kt | 33 +++++++++++++++++++ .../OutputResponseDeserializerTests.kt | 12 +++++++ ...wsjson-output-response-deserializer.smithy | 4 +++ 6 files changed, 60 insertions(+), 30 deletions(-) create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt index 6a157ffbf..05e6f0ba7 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt @@ -31,7 +31,7 @@ class HttpResponseTraitPayload( override fun render() { val httpPayload = responseBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD } if (httpPayload != null) { - HttpResponseTraitWithHttpPayload(ctx, httpPayload, writer).render() + HttpResponseTraitWithHttpPayload(ctx, responseBindings, httpPayload, writer).render() } else { val httpResponseTraitWithoutPayload = httpResponseTraitWithoutPayloadFactory?.let { it.construct(ctx, responseBindings, outputShape, writer) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt index 0806f82aa..e8f8591f7 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt @@ -7,6 +7,7 @@ package software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTra import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.shapes.ShapeType +import software.amazon.smithy.model.knowledge.HttpBinding import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.swift.codegen.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.SwiftTypes @@ -18,9 +19,11 @@ import software.amazon.smithy.swift.codegen.integration.SectionId import software.amazon.smithy.swift.codegen.integration.httpResponse.HttpResponseBindingRenderable import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.isEnum +import software.amazon.smithy.swift.codegen.model.targetOrSelf class HttpResponseTraitWithHttpPayload( val ctx: ProtocolGenerator.GenerationContext, + val responseBindings: List, val binding: HttpBindingDescriptor, val writer: SwiftWriter ) : HttpResponseBindingRenderable { @@ -28,6 +31,11 @@ class HttpResponseTraitWithHttpPayload( object MessageDecoderSectionId : SectionId override fun render() { + val bodyMembers = responseBindings.filter { it.location == HttpBinding.Location.DOCUMENT } + val initialResponseMembers = bodyMembers.filter { + val targetShape = it.member.targetOrSelf(ctx.model) + targetShape?.hasTrait(StreamingTrait::class.java) == false + }.toMutableSet() val memberName = ctx.symbolProvider.toMemberName(binding.member) val target = ctx.model.expectShape(binding.member.target) val symbol = ctx.symbolProvider.toSymbol(target) @@ -97,6 +105,7 @@ class HttpResponseTraitWithHttpPayload( } writer.write("let decoderStream = \$L<\$N>(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder)", ClientRuntimeTypes.EventStream.MessageDecoderStream, symbol) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) + writeInitialResponseMembers(ctx, writer, initialResponseMembers) } } else { writer.openBlock("if let data = try await httpResponse.body.readData(), let responseDecoder = decoder {", "} else {") { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index d284f0910..1045f359b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -77,7 +77,7 @@ class HttpResponseTraitWithoutHttpPayload( symbol ) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) - writeInitialResponseMembers(initialResponseMembers) + writeInitialResponseMembers(ctx, writer, initialResponseMembers) } writer.indent() writer.write("self.\$L = nil", memberName).closeBlock("}") @@ -136,33 +136,5 @@ class HttpResponseTraitWithoutHttpPayload( writer.write("}") } - fun writeInitialResponseMembers(initialResponseMembers: Set) { - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - writer.apply { - write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") - indent() - write("let decoder = JSONDecoder()") - write("do {") - indent() - write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") - write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") - dedent() - write("} catch {") - indent() - write("print(\"Error decoding JSON: \\(error)\")") - write("self.$responseMemberName = nil") - dedent() - write("}") - dedent() - write("} else {") - indent() - write("self.$responseMemberName = nil") - dedent() - write("}") - } - } - } - private val path: String = "properties.".takeIf { outputShape.hasTrait() } ?: "" } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt new file mode 100644 index 000000000..fed210164 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt @@ -0,0 +1,33 @@ +package software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits + +import software.amazon.smithy.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.integration.HttpBindingDescriptor +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator + +fun writeInitialResponseMembers(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, initialResponseMembers: Set) { + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + writer.apply { + write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") + indent() + write("let decoder = JSONDecoder()") + write("do {") + indent() + write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") + dedent() + write("} catch {") + indent() + write("print(\"Error decoding JSON: \\(error)\")") + write("self.$responseMemberName = nil") + dedent() + write("}") + dedent() + write("} else {") + indent() + write("self.$responseMemberName = nil") + dedent() + write("}") + } + } +} diff --git a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt index 52ee60b6a..5ade97856 100644 --- a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt @@ -98,6 +98,18 @@ class OutputResponseDeserializerTests { let messageDecoder: ClientRuntime.MessageDecoder? = nil let decoderStream = ClientRuntime.EventStream.DefaultMessageDecoderStream(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder) self.eventStream = decoderStream.toAsyncStream() + if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() { + let decoder = JSONDecoder() + do { + let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp) + self.metadata = response["metadata"].map { value in KinesisClientTypes.Tag(value: value) } + } catch { + print("Error decoding JSON: \(error)") + self.metadata = nil + } + } else { + self.metadata = nil + } } else { self.eventStream = nil } diff --git a/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy b/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy index 1f5e65128..5df2e144a 100644 --- a/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy +++ b/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy @@ -51,6 +51,10 @@ operation EventStreaming { } structure EventStreamingOutput { + @httpLabel + @required + metadata: String + @required eventStream: EventStream } From 200781ab12668da0c1bf7cf4d60f6eb68ba92b42 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 14 Sep 2023 18:45:44 -0400 Subject: [PATCH 04/18] rename initialData --- .../integration/httpResponse/bindingTraits/ResponseHelpers.kt | 4 ++-- .../kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt index fed210164..37e68d1ec 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt @@ -8,12 +8,12 @@ fun writeInitialResponseMembers(ctx: ProtocolGenerator.GenerationContext, writer initialResponseMembers.forEach { responseMember -> val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) writer.apply { - write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") + write("if let initialData = await messageDecoder.awaitInitialResponse() {") indent() write("let decoder = JSONDecoder()") write("do {") indent() - write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + write("let response = try decoder.decode([String: String].self, from: initialData)") write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") dedent() write("} catch {") diff --git a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt index 5ade97856..f246c44ba 100644 --- a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt @@ -98,10 +98,10 @@ class OutputResponseDeserializerTests { let messageDecoder: ClientRuntime.MessageDecoder? = nil let decoderStream = ClientRuntime.EventStream.DefaultMessageDecoderStream(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder) self.eventStream = decoderStream.toAsyncStream() - if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() { + if let initialData = await messageDecoder.awaitInitialResponse() { let decoder = JSONDecoder() do { - let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp) + let response = try decoder.decode([String: String].self, from: initialData) self.metadata = response["metadata"].map { value in KinesisClientTypes.Tag(value: value) } } catch { print("Error decoding JSON: \(error)") From 8605a5b38f0b20725065cb40270074d0d5b8c360 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Mon, 18 Sep 2023 12:00:45 -0700 Subject: [PATCH 05/18] reorder imports --- .../bindingTraits/HttpResponseTraitWithHttpPayload.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt index e8f8591f7..f66ac1a36 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt @@ -6,8 +6,8 @@ package software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits import software.amazon.smithy.codegen.core.CodegenException -import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.knowledge.HttpBinding +import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.swift.codegen.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.SwiftTypes From db2baf50f36d36293b65b31cab67ba20315721de Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 19 Sep 2023 17:33:15 -0700 Subject: [PATCH 06/18] support initial-request and add tests --- .../handlers/HttpBodyMiddleware.kt | 55 +++++++-- .../test/kotlin/HttpBodyMiddlewareTests.kt | 113 ++++++++++++++++++ ...ttp-binding-protocol-generator-test.smithy | 40 ++++++- 3 files changed, 197 insertions(+), 11 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index 6174937b8..d6a1b1238 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -25,6 +25,7 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.steps.OperationSerializeStep import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.model.targetOrSelf class HttpBodyMiddleware( private val writer: SwiftWriter, @@ -71,12 +72,19 @@ class HttpBodyMiddleware( private fun renderEncodedBody() { val httpPayload = requestBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD } + val bodyMembers = requestBindings.filter { + it.location == HttpBinding.Location.DOCUMENT || it.location == HttpBinding.Location.LABEL + } + val initialRequestMembers = bodyMembers.filter { + val targetShape = it.member.targetOrSelf(ctx.model) + targetShape?.hasTrait(StreamingTrait::class.java) == false + }.toMutableSet() if (httpPayload != null) { - renderExplicitPayload(httpPayload) + renderExplicitPayload(httpPayload, initialRequestMembers) } } - private fun renderExplicitPayload(binding: HttpBindingDescriptor) { + private fun renderExplicitPayload(binding: HttpBindingDescriptor, initialRequestMembers: Set) { val memberName = ctx.symbolProvider.toMemberName(binding.member) val target = ctx.model.expectShape(binding.member.target) val dataDeclaration = "${memberName}Data" @@ -124,10 +132,14 @@ class HttpBodyMiddleware( writer.openBlock("guard let messageSigner = context.getMessageSigner() else {", "}") { writer.write("fatalError(\"Message signer is required for streaming payload\")") } - writer.write( - "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: xmlEncoder, messageSinger: messageSigner)", - ClientRuntimeTypes.EventStream.MessageEncoderStream - ) + if (initialRequestMembers.isNotEmpty()) { + renderWithInitialRequest(memberName) + } else { + writer.write( + "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + ClientRuntimeTypes.EventStream.MessageEncoderStream + ) + } writer.write("input.builder.withBody(.stream(encoderStream))") } else { writer.write("let $dataDeclaration = try xmlEncoder.encode(\$L, withRootKey: \"\$L\")", memberName, xmlName) @@ -141,10 +153,14 @@ class HttpBodyMiddleware( writer.openBlock("guard let messageSigner = context.getMessageSigner() else {", "}") { writer.write("fatalError(\"Message signer is required for streaming payload\")") } - writer.write( - "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", - ClientRuntimeTypes.EventStream.MessageEncoderStream - ) + if (initialRequestMembers.isNotEmpty()) { + renderWithInitialRequest(memberName) + } else { + writer.write( + "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + ClientRuntimeTypes.EventStream.MessageEncoderStream + ) + } writer.write("input.builder.withBody(.stream(encoderStream))") } else { writer.write("let $dataDeclaration = try encoder.encode(\$L)", memberName) @@ -177,6 +193,25 @@ class HttpBodyMiddleware( } } + private fun renderWithInitialRequest(memberName: String) { + // Encode initialRequestMembers to a message + writer.write("let jsonData = try JSONEncoder().encode(initialRequestMembers)") + writer.write("let jsonString = String(data: jsonData, encoding: .utf8)!") + writer.write("let initialMessage = EventStream.Message(") + writer.indent() + writer.openBlock("headers: [", "],") { + writer.write(".init(name: \":event-type\", value: .string(\"initial-request\")),") + writer.write(".init(name: \":message-type\", value: .string(\"event\")),") + } + writer.write("payload: jsonString.data(using: .utf8)!") + writer.dedent() + writer.write(")") + // add initial-request message to front of the stream + writer.write("let encoderStream = \$L(stream: initialMessage + $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + ClientRuntimeTypes.EventStream.MessageEncoderStream + ) + } + private fun renderEncodedBodyAddedToRequest( memberName: String, bodyDeclaration: String, diff --git a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt index 86b65a96a..607501361 100644 --- a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt @@ -174,4 +174,117 @@ class HttpBodyMiddlewareTests { """.trimIndent() contents.shouldContainOnlyOnce(expectedContents) } + + @Test + fun `it builds body middleware for event streams with initial request`() { + val contents = getModelFileContents("example", "PublishMessagesInitialRequestInput+BodyMiddleware.swift", newTestContext.manifest) + contents.shouldSyntacticSanityCheck() + val expectedContents = + """ + public struct PublishMessagesInitialRequestInputBodyMiddleware: ClientRuntime.Middleware { + public let id: Swift.String = "PublishMessagesInitialRequestInputBodyMiddleware" + + public init() {} + + public func handle(context: Context, + input: ClientRuntime.SerializeStepInput, + next: H) async throws -> ClientRuntime.OperationOutput + where H: Handler, + Self.MInput == H.Input, + Self.MOutput == H.Output, + Self.Context == H.Context + { + do { + let encoder = context.getEncoder() + if let messages = input.operationInput.messages { + guard let messageEncoder = context.getMessageEncoder() else { + fatalError("Message encoder is required for streaming payload") + } + guard let messageSigner = context.getMessageSigner() else { + fatalError("Message signer is required for streaming payload") + } + let jsonData = try JSONEncoder().encode(initialRequestMembers) + let jsonString = String(data: jsonData, encoding: .utf8)! + let initialMessage = EventStream.Message( + headers: [ + .init(name: ":event-type", value: .string("initial-request")), + .init(name: ":message-type", value: .string("event")), + ], + payload: jsonString.data(using: .utf8)! + ) + let encoderStream = ClientRuntime.EventStream.DefaultMessageEncoderStream(stream: initialMessage + messages, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner) + input.builder.withBody(.stream(encoderStream)) + } else { + if encoder is JSONEncoder { + // Encode an empty body as an empty structure in JSON + let messagesData = "{}".data(using: .utf8)! + let messagesBody = ClientRuntime.HttpBody.data(messagesData) + input.builder.withBody(messagesBody) + } + } + } catch let err { + throw ClientRuntime.ClientError.unknownError(err.localizedDescription) + } + return try await next.handle(context: context, input: input) + } + + public typealias MInput = ClientRuntime.SerializeStepInput + public typealias MOutput = ClientRuntime.OperationOutput + public typealias Context = ClientRuntime.HttpContext + } + """.trimIndent() + contents.shouldContainOnlyOnce(expectedContents) + } + + @Test + fun `it builds body middleware for event streams without initial request`() { + val contents = getModelFileContents("example", "PublishMessagesNoInitialRequestInput+BodyMiddleware.swift", newTestContext.manifest) + contents.shouldSyntacticSanityCheck() + val expectedContents = + """ + public struct PublishMessagesNoInitialRequestInputBodyMiddleware: ClientRuntime.Middleware { + public let id: Swift.String = "PublishMessagesNoInitialRequestInputBodyMiddleware" + + public init() {} + + public func handle(context: Context, + input: ClientRuntime.SerializeStepInput, + next: H) async throws -> ClientRuntime.OperationOutput + where H: Handler, + Self.MInput == H.Input, + Self.MOutput == H.Output, + Self.Context == H.Context + { + do { + let encoder = context.getEncoder() + if let messages = input.operationInput.messages { + guard let messageEncoder = context.getMessageEncoder() else { + fatalError("Message encoder is required for streaming payload") + } + guard let messageSigner = context.getMessageSigner() else { + fatalError("Message signer is required for streaming payload") + } + let encoderStream = ClientRuntime.EventStream.DefaultMessageEncoderStream(stream: messages, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner) + input.builder.withBody(.stream(encoderStream)) + } else { + if encoder is JSONEncoder { + // Encode an empty body as an empty structure in JSON + let messagesData = "{}".data(using: .utf8)! + let messagesBody = ClientRuntime.HttpBody.data(messagesData) + input.builder.withBody(messagesBody) + } + } + } catch let err { + throw ClientRuntime.ClientError.unknownError(err.localizedDescription) + } + return try await next.handle(context: context, input: input) + } + + public typealias MInput = ClientRuntime.SerializeStepInput + public typealias MOutput = ClientRuntime.OperationOutput + public typealias Context = ClientRuntime.HttpContext + } + """.trimIndent() + contents.shouldContainOnlyOnce(expectedContents) + } } diff --git a/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy b/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy index 40c7502d4..0bdde7049 100644 --- a/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy +++ b/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy @@ -40,7 +40,9 @@ service Example { IdempotencyTokenWithoutHttpPayloadTraitOnToken, InlineDocument, InlineDocumentAsPayload, - RequiredHttpFields + RequiredHttpFields, + PublishMessagesInitialRequest, + PublishMessagesNoInitialRequest ] } @@ -1411,3 +1413,39 @@ structure RequiredHttpFieldsInput { @length(min: 1) query3: String } + +@http(method: "POST", uri: "/messages/{room}") +operation PublishMessagesInitialRequest { + input: PublishMessagesInputInitialRequest +} + +@input +structure PublishMessagesInputInitialRequest { + @httpLabel + @required + room: String + + @httpPayload + messages: MessageStream +} + + +@http(method: "POST", uri: "/messages") +operation PublishMessagesNoInitialRequest { + input: PublishMessagesInputNoInitialRequest +} + +@input +structure PublishMessagesInputNoInitialRequest { + @httpPayload + messages: MessageStream +} + +@streaming +union MessageStream { + message: Message +} + +structure Message { + message: String +} From ebb76160794804766ce787ef3fc6737a836f5afb Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 19 Sep 2023 17:46:17 -0700 Subject: [PATCH 07/18] lint --- .../integration/middlewares/handlers/HttpBodyMiddleware.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index d6a1b1238..a85ae11f9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -207,7 +207,8 @@ class HttpBodyMiddleware( writer.dedent() writer.write(")") // add initial-request message to front of the stream - writer.write("let encoderStream = \$L(stream: initialMessage + $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + writer.write( + "let encoderStream = \$L(stream: initialMessage + $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", ClientRuntimeTypes.EventStream.MessageEncoderStream ) } From 49b2e737dc1e59a7065adda3d8b0b634c477655e Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 21 Sep 2023 10:49:28 -0700 Subject: [PATCH 08/18] fix swift codegen to properly parse initial request members --- .../middlewares/handlers/HttpBodyMiddleware.kt | 16 ++++++++++++---- .../src/test/kotlin/HttpBodyMiddlewareTests.kt | 5 ++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index a85ae11f9..ec0d223a6 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -133,7 +133,7 @@ class HttpBodyMiddleware( writer.write("fatalError(\"Message signer is required for streaming payload\")") } if (initialRequestMembers.isNotEmpty()) { - renderWithInitialRequest(memberName) + renderWithInitialRequest(memberName, initialRequestMembers) } else { writer.write( "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", @@ -154,7 +154,7 @@ class HttpBodyMiddleware( writer.write("fatalError(\"Message signer is required for streaming payload\")") } if (initialRequestMembers.isNotEmpty()) { - renderWithInitialRequest(memberName) + renderWithInitialRequest(memberName, initialRequestMembers) } else { writer.write( "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", @@ -193,10 +193,18 @@ class HttpBodyMiddleware( } } - private fun renderWithInitialRequest(memberName: String) { + private fun renderWithInitialRequest(memberName: String, initialRequestMembers: Set) { // Encode initialRequestMembers to a message + val entries = initialRequestMembers.map { + "\"${it.memberName}\": input.operationInput.${it.memberName}" + }.joinToString(", ") + writer.write("let initialRequestMembers: [String: Any] = [$entries]") writer.write("let jsonData = try JSONEncoder().encode(initialRequestMembers)") - writer.write("let jsonString = String(data: jsonData, encoding: .utf8)!") + writer.write("guard let jsonString = String(data: jsonData, encoding: .utf8) else {") + writer.indent() + writer.write("fatalError(\"Failed to decode JSON data to string.\")") + writer.dedent() + writer.write("}") writer.write("let initialMessage = EventStream.Message(") writer.indent() writer.openBlock("headers: [", "],") { diff --git a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt index 607501361..806f9e836 100644 --- a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt @@ -203,8 +203,11 @@ class HttpBodyMiddlewareTests { guard let messageSigner = context.getMessageSigner() else { fatalError("Message signer is required for streaming payload") } + let initialRequestMembers: [String: Any] = ["room": input.operationInput.room] let jsonData = try JSONEncoder().encode(initialRequestMembers) - let jsonString = String(data: jsonData, encoding: .utf8)! + guard let jsonString = String(data: jsonData, encoding: .utf8) else { + fatalError("Failed to decode JSON data to string.") + } let initialMessage = EventStream.Message( headers: [ .init(name: ":event-type", value: .string("initial-request")), From d837a55398e165102a2ec4fac35704e017c329d9 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 3 Oct 2023 12:13:39 -0400 Subject: [PATCH 09/18] remove initial-request support --- .../HttpResponseTraitWithHttpPayload.kt | 5 - .../bindingTraits/ResponseHelpers.kt | 33 ----- .../handlers/HttpBodyMiddleware.kt | 60 ++------- .../test/kotlin/HttpBodyMiddlewareTests.kt | 116 ------------------ ...ttp-binding-protocol-generator-test.smithy | 40 +----- 5 files changed, 11 insertions(+), 243 deletions(-) delete mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt index f66ac1a36..746aed6fb 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt @@ -32,10 +32,6 @@ class HttpResponseTraitWithHttpPayload( override fun render() { val bodyMembers = responseBindings.filter { it.location == HttpBinding.Location.DOCUMENT } - val initialResponseMembers = bodyMembers.filter { - val targetShape = it.member.targetOrSelf(ctx.model) - targetShape?.hasTrait(StreamingTrait::class.java) == false - }.toMutableSet() val memberName = ctx.symbolProvider.toMemberName(binding.member) val target = ctx.model.expectShape(binding.member.target) val symbol = ctx.symbolProvider.toSymbol(target) @@ -105,7 +101,6 @@ class HttpResponseTraitWithHttpPayload( } writer.write("let decoderStream = \$L<\$N>(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder)", ClientRuntimeTypes.EventStream.MessageDecoderStream, symbol) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) - writeInitialResponseMembers(ctx, writer, initialResponseMembers) } } else { writer.openBlock("if let data = try await httpResponse.body.readData(), let responseDecoder = decoder {", "} else {") { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt deleted file mode 100644 index 37e68d1ec..000000000 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/ResponseHelpers.kt +++ /dev/null @@ -1,33 +0,0 @@ -package software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits - -import software.amazon.smithy.swift.codegen.SwiftWriter -import software.amazon.smithy.swift.codegen.integration.HttpBindingDescriptor -import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator - -fun writeInitialResponseMembers(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, initialResponseMembers: Set) { - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - writer.apply { - write("if let initialData = await messageDecoder.awaitInitialResponse() {") - indent() - write("let decoder = JSONDecoder()") - write("do {") - indent() - write("let response = try decoder.decode([String: String].self, from: initialData)") - write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") - dedent() - write("} catch {") - indent() - write("print(\"Error decoding JSON: \\(error)\")") - write("self.$responseMemberName = nil") - dedent() - write("}") - dedent() - write("} else {") - indent() - write("self.$responseMemberName = nil") - dedent() - write("}") - } - } -} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index ec0d223a6..54d878ed9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -75,16 +75,12 @@ class HttpBodyMiddleware( val bodyMembers = requestBindings.filter { it.location == HttpBinding.Location.DOCUMENT || it.location == HttpBinding.Location.LABEL } - val initialRequestMembers = bodyMembers.filter { - val targetShape = it.member.targetOrSelf(ctx.model) - targetShape?.hasTrait(StreamingTrait::class.java) == false - }.toMutableSet() if (httpPayload != null) { - renderExplicitPayload(httpPayload, initialRequestMembers) + renderExplicitPayload(httpPayload) } } - private fun renderExplicitPayload(binding: HttpBindingDescriptor, initialRequestMembers: Set) { + private fun renderExplicitPayload(binding: HttpBindingDescriptor) { val memberName = ctx.symbolProvider.toMemberName(binding.member) val target = ctx.model.expectShape(binding.member.target) val dataDeclaration = "${memberName}Data" @@ -132,14 +128,10 @@ class HttpBodyMiddleware( writer.openBlock("guard let messageSigner = context.getMessageSigner() else {", "}") { writer.write("fatalError(\"Message signer is required for streaming payload\")") } - if (initialRequestMembers.isNotEmpty()) { - renderWithInitialRequest(memberName, initialRequestMembers) - } else { - writer.write( - "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", - ClientRuntimeTypes.EventStream.MessageEncoderStream - ) - } + writer.write( + "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + ClientRuntimeTypes.EventStream.MessageEncoderStream + ) writer.write("input.builder.withBody(.stream(encoderStream))") } else { writer.write("let $dataDeclaration = try xmlEncoder.encode(\$L, withRootKey: \"\$L\")", memberName, xmlName) @@ -153,14 +145,10 @@ class HttpBodyMiddleware( writer.openBlock("guard let messageSigner = context.getMessageSigner() else {", "}") { writer.write("fatalError(\"Message signer is required for streaming payload\")") } - if (initialRequestMembers.isNotEmpty()) { - renderWithInitialRequest(memberName, initialRequestMembers) - } else { - writer.write( - "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", - ClientRuntimeTypes.EventStream.MessageEncoderStream - ) - } + writer.write( + "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + ClientRuntimeTypes.EventStream.MessageEncoderStream + ) writer.write("input.builder.withBody(.stream(encoderStream))") } else { writer.write("let $dataDeclaration = try encoder.encode(\$L)", memberName) @@ -193,34 +181,6 @@ class HttpBodyMiddleware( } } - private fun renderWithInitialRequest(memberName: String, initialRequestMembers: Set) { - // Encode initialRequestMembers to a message - val entries = initialRequestMembers.map { - "\"${it.memberName}\": input.operationInput.${it.memberName}" - }.joinToString(", ") - writer.write("let initialRequestMembers: [String: Any] = [$entries]") - writer.write("let jsonData = try JSONEncoder().encode(initialRequestMembers)") - writer.write("guard let jsonString = String(data: jsonData, encoding: .utf8) else {") - writer.indent() - writer.write("fatalError(\"Failed to decode JSON data to string.\")") - writer.dedent() - writer.write("}") - writer.write("let initialMessage = EventStream.Message(") - writer.indent() - writer.openBlock("headers: [", "],") { - writer.write(".init(name: \":event-type\", value: .string(\"initial-request\")),") - writer.write(".init(name: \":message-type\", value: .string(\"event\")),") - } - writer.write("payload: jsonString.data(using: .utf8)!") - writer.dedent() - writer.write(")") - // add initial-request message to front of the stream - writer.write( - "let encoderStream = \$L(stream: initialMessage + $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", - ClientRuntimeTypes.EventStream.MessageEncoderStream - ) - } - private fun renderEncodedBodyAddedToRequest( memberName: String, bodyDeclaration: String, diff --git a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt index 806f9e836..86b65a96a 100644 --- a/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/HttpBodyMiddlewareTests.kt @@ -174,120 +174,4 @@ class HttpBodyMiddlewareTests { """.trimIndent() contents.shouldContainOnlyOnce(expectedContents) } - - @Test - fun `it builds body middleware for event streams with initial request`() { - val contents = getModelFileContents("example", "PublishMessagesInitialRequestInput+BodyMiddleware.swift", newTestContext.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = - """ - public struct PublishMessagesInitialRequestInputBodyMiddleware: ClientRuntime.Middleware { - public let id: Swift.String = "PublishMessagesInitialRequestInputBodyMiddleware" - - public init() {} - - public func handle(context: Context, - input: ClientRuntime.SerializeStepInput, - next: H) async throws -> ClientRuntime.OperationOutput - where H: Handler, - Self.MInput == H.Input, - Self.MOutput == H.Output, - Self.Context == H.Context - { - do { - let encoder = context.getEncoder() - if let messages = input.operationInput.messages { - guard let messageEncoder = context.getMessageEncoder() else { - fatalError("Message encoder is required for streaming payload") - } - guard let messageSigner = context.getMessageSigner() else { - fatalError("Message signer is required for streaming payload") - } - let initialRequestMembers: [String: Any] = ["room": input.operationInput.room] - let jsonData = try JSONEncoder().encode(initialRequestMembers) - guard let jsonString = String(data: jsonData, encoding: .utf8) else { - fatalError("Failed to decode JSON data to string.") - } - let initialMessage = EventStream.Message( - headers: [ - .init(name: ":event-type", value: .string("initial-request")), - .init(name: ":message-type", value: .string("event")), - ], - payload: jsonString.data(using: .utf8)! - ) - let encoderStream = ClientRuntime.EventStream.DefaultMessageEncoderStream(stream: initialMessage + messages, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner) - input.builder.withBody(.stream(encoderStream)) - } else { - if encoder is JSONEncoder { - // Encode an empty body as an empty structure in JSON - let messagesData = "{}".data(using: .utf8)! - let messagesBody = ClientRuntime.HttpBody.data(messagesData) - input.builder.withBody(messagesBody) - } - } - } catch let err { - throw ClientRuntime.ClientError.unknownError(err.localizedDescription) - } - return try await next.handle(context: context, input: input) - } - - public typealias MInput = ClientRuntime.SerializeStepInput - public typealias MOutput = ClientRuntime.OperationOutput - public typealias Context = ClientRuntime.HttpContext - } - """.trimIndent() - contents.shouldContainOnlyOnce(expectedContents) - } - - @Test - fun `it builds body middleware for event streams without initial request`() { - val contents = getModelFileContents("example", "PublishMessagesNoInitialRequestInput+BodyMiddleware.swift", newTestContext.manifest) - contents.shouldSyntacticSanityCheck() - val expectedContents = - """ - public struct PublishMessagesNoInitialRequestInputBodyMiddleware: ClientRuntime.Middleware { - public let id: Swift.String = "PublishMessagesNoInitialRequestInputBodyMiddleware" - - public init() {} - - public func handle(context: Context, - input: ClientRuntime.SerializeStepInput, - next: H) async throws -> ClientRuntime.OperationOutput - where H: Handler, - Self.MInput == H.Input, - Self.MOutput == H.Output, - Self.Context == H.Context - { - do { - let encoder = context.getEncoder() - if let messages = input.operationInput.messages { - guard let messageEncoder = context.getMessageEncoder() else { - fatalError("Message encoder is required for streaming payload") - } - guard let messageSigner = context.getMessageSigner() else { - fatalError("Message signer is required for streaming payload") - } - let encoderStream = ClientRuntime.EventStream.DefaultMessageEncoderStream(stream: messages, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner) - input.builder.withBody(.stream(encoderStream)) - } else { - if encoder is JSONEncoder { - // Encode an empty body as an empty structure in JSON - let messagesData = "{}".data(using: .utf8)! - let messagesBody = ClientRuntime.HttpBody.data(messagesData) - input.builder.withBody(messagesBody) - } - } - } catch let err { - throw ClientRuntime.ClientError.unknownError(err.localizedDescription) - } - return try await next.handle(context: context, input: input) - } - - public typealias MInput = ClientRuntime.SerializeStepInput - public typealias MOutput = ClientRuntime.OperationOutput - public typealias Context = ClientRuntime.HttpContext - } - """.trimIndent() - contents.shouldContainOnlyOnce(expectedContents) - } } diff --git a/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy b/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy index 0bdde7049..40c7502d4 100644 --- a/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy +++ b/smithy-swift-codegen/src/test/resources/http-binding-protocol-generator-test.smithy @@ -40,9 +40,7 @@ service Example { IdempotencyTokenWithoutHttpPayloadTraitOnToken, InlineDocument, InlineDocumentAsPayload, - RequiredHttpFields, - PublishMessagesInitialRequest, - PublishMessagesNoInitialRequest + RequiredHttpFields ] } @@ -1413,39 +1411,3 @@ structure RequiredHttpFieldsInput { @length(min: 1) query3: String } - -@http(method: "POST", uri: "/messages/{room}") -operation PublishMessagesInitialRequest { - input: PublishMessagesInputInitialRequest -} - -@input -structure PublishMessagesInputInitialRequest { - @httpLabel - @required - room: String - - @httpPayload - messages: MessageStream -} - - -@http(method: "POST", uri: "/messages") -operation PublishMessagesNoInitialRequest { - input: PublishMessagesInputNoInitialRequest -} - -@input -structure PublishMessagesInputNoInitialRequest { - @httpPayload - messages: MessageStream -} - -@streaming -union MessageStream { - message: Message -} - -structure Message { - message: String -} From 664b86fc928dcaa53ab0427bcfb0fc0e6744276e Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 3 Oct 2023 15:03:33 -0400 Subject: [PATCH 10/18] remove initial-request support + add initial-response codegen test --- .../bindingTraits/HttpResponseTraitPayload.kt | 2 +- .../HttpResponseTraitWithHttpPayload.kt | 2 - .../HttpResponseTraitWithoutHttpPayload.kt | 42 ++++++++++++++++++- .../handlers/HttpBodyMiddleware.kt | 5 +-- .../MockHttpAWSJson11ProtocolGenerator.kt | 3 +- .../OutputResponseDeserializerTests.kt | 12 ------ 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt index 05e6f0ba7..6a157ffbf 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitPayload.kt @@ -31,7 +31,7 @@ class HttpResponseTraitPayload( override fun render() { val httpPayload = responseBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD } if (httpPayload != null) { - HttpResponseTraitWithHttpPayload(ctx, responseBindings, httpPayload, writer).render() + HttpResponseTraitWithHttpPayload(ctx, httpPayload, writer).render() } else { val httpResponseTraitWithoutPayload = httpResponseTraitWithoutPayloadFactory?.let { it.construct(ctx, responseBindings, outputShape, writer) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt index 746aed6fb..fe1c603d0 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt @@ -23,7 +23,6 @@ import software.amazon.smithy.swift.codegen.model.targetOrSelf class HttpResponseTraitWithHttpPayload( val ctx: ProtocolGenerator.GenerationContext, - val responseBindings: List, val binding: HttpBindingDescriptor, val writer: SwiftWriter ) : HttpResponseBindingRenderable { @@ -31,7 +30,6 @@ class HttpResponseTraitWithHttpPayload( object MessageDecoderSectionId : SectionId override fun render() { - val bodyMembers = responseBindings.filter { it.location == HttpBinding.Location.DOCUMENT } val memberName = ctx.symbolProvider.toMemberName(binding.member) val target = ctx.model.expectShape(binding.member.target) val symbol = ctx.symbolProvider.toSymbol(target) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index 1045f359b..05269f4dd 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -77,7 +77,7 @@ class HttpResponseTraitWithoutHttpPayload( symbol ) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) - writeInitialResponseMembers(ctx, writer, initialResponseMembers) + writeInitialResponseMembers(initialResponseMembers) } writer.indent() writer.write("self.\$L = nil", memberName).closeBlock("}") @@ -137,4 +137,44 @@ class HttpResponseTraitWithoutHttpPayload( } private val path: String = "properties.".takeIf { outputShape.hasTrait() } ?: "" + + private fun writeInitialResponseMembers(initialResponseMembers: Set) { + writer.apply { + write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") + indent() + write("let decoder = JSONDecoder()") + write("do {") + indent() + write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") + } + + dedent() + write("} catch {") + indent() + write("print(\"Error decoding JSON: \\(error)\")") + + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = nil") + } + + dedent() + write("}") + dedent() + write("} else {") + indent() + + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = nil") + } + + dedent() + write("}") + } + } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index 54d878ed9..0cfd6d0c4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -72,9 +72,6 @@ class HttpBodyMiddleware( private fun renderEncodedBody() { val httpPayload = requestBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD } - val bodyMembers = requestBindings.filter { - it.location == HttpBinding.Location.DOCUMENT || it.location == HttpBinding.Location.LABEL - } if (httpPayload != null) { renderExplicitPayload(httpPayload) } @@ -129,7 +126,7 @@ class HttpBodyMiddleware( writer.write("fatalError(\"Message signer is required for streaming payload\")") } writer.write( - "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: encoder, messageSinger: messageSigner)", + "let encoderStream = \$L(stream: $memberName, messageEncoder: messageEncoder, requestEncoder: xmlEncoder, messageSinger: messageSigner)", ClientRuntimeTypes.EventStream.MessageEncoderStream ) writer.write("input.builder.withBody(.stream(encoderStream))") diff --git a/smithy-swift-codegen/src/test/kotlin/mocks/MockHttpAWSJson11ProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/mocks/MockHttpAWSJson11ProtocolGenerator.kt index 52c9ee6c9..c2abc090c 100644 --- a/smithy-swift-codegen/src/test/kotlin/mocks/MockHttpAWSJson11ProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/mocks/MockHttpAWSJson11ProtocolGenerator.kt @@ -53,7 +53,8 @@ class MockAWSJson11HttpProtocolCustomizations() : DefaultHttpProtocolCustomizati writer: SwiftWriter, op: OperationShape, ) { - TODO("Not yet implemented") + // Not yet implemented + return } } diff --git a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt index f246c44ba..52ee60b6a 100644 --- a/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/serde/awsjson11/OutputResponseDeserializerTests.kt @@ -98,18 +98,6 @@ class OutputResponseDeserializerTests { let messageDecoder: ClientRuntime.MessageDecoder? = nil let decoderStream = ClientRuntime.EventStream.DefaultMessageDecoderStream(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder) self.eventStream = decoderStream.toAsyncStream() - if let initialData = await messageDecoder.awaitInitialResponse() { - let decoder = JSONDecoder() - do { - let response = try decoder.decode([String: String].self, from: initialData) - self.metadata = response["metadata"].map { value in KinesisClientTypes.Tag(value: value) } - } catch { - print("Error decoding JSON: \(error)") - self.metadata = nil - } - } else { - self.metadata = nil - } } else { self.eventStream = nil } From f55e2f0c3baae500335d1ed53426959a447c60d7 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 3 Oct 2023 15:35:34 -0400 Subject: [PATCH 11/18] remove unnecessary imports --- .../bindingTraits/HttpResponseTraitWithHttpPayload.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt index fe1c603d0..0806f82aa 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithHttpPayload.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits import software.amazon.smithy.codegen.core.CodegenException -import software.amazon.smithy.model.knowledge.HttpBinding import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.swift.codegen.ClientRuntimeTypes @@ -19,7 +18,6 @@ import software.amazon.smithy.swift.codegen.integration.SectionId import software.amazon.smithy.swift.codegen.integration.httpResponse.HttpResponseBindingRenderable import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.isEnum -import software.amazon.smithy.swift.codegen.model.targetOrSelf class HttpResponseTraitWithHttpPayload( val ctx: ProtocolGenerator.GenerationContext, From 7eceb6dbb804d32b3509f78300403163ab7c986f Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 3 Oct 2023 15:37:40 -0400 Subject: [PATCH 12/18] add test files --- .../EventStreamsInitialResponseTests.kt | 74 +++++++++++++++++++ ...ent-stream-initial-request-response.smithy | 39 ++++++++++ ...wsjson-output-response-deserializer.smithy | 4 - 3 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt create mode 100644 smithy-swift-codegen/src/test/resources/event-stream-initial-request-response.smithy diff --git a/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt new file mode 100644 index 000000000..13068dedf --- /dev/null +++ b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +import io.kotest.matchers.string.shouldContainOnlyOnce +import mocks.MockHttpAWSJson11ProtocolGenerator +import org.junit.jupiter.api.Test +import software.amazon.smithy.swift.codegen.integration.HttpBindingProtocolGenerator + +class EventStreamsInitialResponseTests { + + @Test + fun `should attempt to decode response if initial-response members are present in RPC (awsJson) smithy model`() { + val context = setupInitialMessageTests( + "event-stream-initial-request-response.smithy", + "com.test#Example", + MockHttpAWSJson11ProtocolGenerator() + ) + val contents = getFileContents( + context.manifest, + "/InitialMessageEventStreams/models/TestStreamOperationWithInitialRequestResponseOutputResponse+HttpResponseBinding.swift" + ) + contents.shouldSyntacticSanityCheck() + contents.shouldContainOnlyOnce( + """ + extension TestStreamOperationWithInitialRequestResponseOutputResponse: ClientRuntime.HttpResponseBinding { + public init(httpResponse: ClientRuntime.HttpResponse, decoder: ClientRuntime.ResponseDecoder? = nil) async throws { + if case let .stream(stream) = httpResponse.body, let responseDecoder = decoder { + let messageDecoder: ClientRuntime.MessageDecoder? = nil + let decoderStream = ClientRuntime.EventStream.DefaultMessageDecoderStream(stream: stream, messageDecoder: messageDecoder, responseDecoder: responseDecoder) + self.value = decoderStream.toAsyncStream() + if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() { + let decoder = JSONDecoder() + do { + let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp) + self.initial1 = response["initial1"].map { value in KinesisClientTypes.Tag(value: value) } + self.initial2 = response["initial2"].map { value in KinesisClientTypes.Tag(value: value) } + } catch { + print("Error decoding JSON: \(error)") + self.initial1 = nil + self.initial2 = nil + } + } else { + self.initial1 = nil + self.initial2 = nil + } + } else { + self.value = nil + } + } + } + """.trimIndent() + ) + } + + + private fun setupInitialMessageTests( + smithyFile: String, + serviceShapeId: String, + protocolGenerator: HttpBindingProtocolGenerator + ): TestContext { + val context = TestContext.initContextFrom(smithyFile, serviceShapeId, protocolGenerator) { model -> + model.defaultSettings(serviceShapeId, "InitialMessageEventStreams", "123", "InitialMessageEventStreams") + } + context.generator.initializeMiddleware(context.generationCtx) + context.generator.generateSerializers(context.generationCtx) + context.generator.generateProtocolClient(context.generationCtx) + context.generator.generateDeserializers(context.generationCtx) + context.generator.generateCodableConformanceForNestedTypes(context.generationCtx) + context.generationCtx.delegator.flushWriters() + return context + } +} \ No newline at end of file diff --git a/smithy-swift-codegen/src/test/resources/event-stream-initial-request-response.smithy b/smithy-swift-codegen/src/test/resources/event-stream-initial-request-response.smithy new file mode 100644 index 000000000..417462aa9 --- /dev/null +++ b/smithy-swift-codegen/src/test/resources/event-stream-initial-request-response.smithy @@ -0,0 +1,39 @@ +namespace com.test + +use aws.protocols#awsJson1_1 +use aws.api#service +use aws.auth#sigv4 + +@awsJson1_1 +@sigv4(name: "event-stream-test") +@service(sdkId: "InitialMessageEventStreams") +service Example { + version: "123", + operations: [TestStreamOperationWithInitialRequestResponse] +} + +operation TestStreamOperationWithInitialRequestResponse { + input: TestStreamInputOutputInitialRequestResponse, + output: TestStreamInputOutputInitialRequestResponse, + errors: [SomeError], +} + +structure TestStreamInputOutputInitialRequestResponse { + @required + value: TestStream + initial1: String + initial2: String +} + +@error("client") +structure SomeError { + Message: String, +} + +structure MessageWithString { @eventPayload data: String } + +@streaming +union TestStream { + MessageWithString: MessageWithString, + SomeError: SomeError, +} \ No newline at end of file diff --git a/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy b/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy index 5df2e144a..1f5e65128 100644 --- a/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy +++ b/smithy-swift-codegen/src/test/resources/serde/awsjson11/awsjson-output-response-deserializer.smithy @@ -51,10 +51,6 @@ operation EventStreaming { } structure EventStreamingOutput { - @httpLabel - @required - metadata: String - @required eventStream: EventStream } From bcfaded3cad9e9a1671c0c3ab937f42d01cf00e2 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 3 Oct 2023 15:56:44 -0400 Subject: [PATCH 13/18] lint --- .../integration/middlewares/handlers/HttpBodyMiddleware.kt | 1 - .../src/test/kotlin/EventStreamsInitialResponseTests.kt | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt index 0cfd6d0c4..6174937b8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/middlewares/handlers/HttpBodyMiddleware.kt @@ -25,7 +25,6 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.steps.OperationSerializeStep import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait -import software.amazon.smithy.swift.codegen.model.targetOrSelf class HttpBodyMiddleware( private val writer: SwiftWriter, diff --git a/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt index 13068dedf..fd71f66fb 100644 --- a/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test import software.amazon.smithy.swift.codegen.integration.HttpBindingProtocolGenerator class EventStreamsInitialResponseTests { - @Test fun `should attempt to decode response if initial-response members are present in RPC (awsJson) smithy model`() { val context = setupInitialMessageTests( @@ -54,7 +53,6 @@ class EventStreamsInitialResponseTests { ) } - private fun setupInitialMessageTests( smithyFile: String, serviceShapeId: String, @@ -71,4 +69,4 @@ class EventStreamsInitialResponseTests { context.generationCtx.delegator.flushWriters() return context } -} \ No newline at end of file +} From a8f5e5ee2a723e607056d6b6e17d4f2f8b190c0e Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 4 Oct 2023 10:49:51 -0400 Subject: [PATCH 14/18] initialResponse code should only apply when members are present + RPC --- .../HttpResponseTraitWithoutHttpPayload.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index 05269f4dd..ee006f9b2 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -77,7 +77,9 @@ class HttpResponseTraitWithoutHttpPayload( symbol ) writer.write("self.\$L = decoderStream.toAsyncStream()", memberName) - writeInitialResponseMembers(initialResponseMembers) + if (isRPCService(ctx) && initialResponseMembers.isNotEmpty()) { + writeInitialResponseMembers(initialResponseMembers) + } } writer.indent() writer.write("self.\$L = nil", memberName).closeBlock("}") @@ -177,4 +179,18 @@ class HttpResponseTraitWithoutHttpPayload( write("}") } } + + private fun isRPCService(ctx: ProtocolGenerator.GenerationContext): Boolean { + return rpcBoundProtocols.contains(ctx.protocol.name) + } + + /** + * A set of RPC-bound Smithy protocols + */ + private val rpcBoundProtocols = setOf( + "awsJson1_0", + "awsJson1_1", + "awsQuery", + "ec2Query", + ) } From 19375bc8e649f073a9bcabb70adf2e78cfb6debb Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 5 Oct 2023 13:30:11 -0400 Subject: [PATCH 15/18] use Set and utility writers --- .../HttpResponseTraitWithoutHttpPayload.kt | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index ee006f9b2..116a4fd2a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -52,7 +52,7 @@ class HttpResponseTraitWithoutHttpPayload( val initialResponseMembers = bodyMembers.filter { val targetShape = it.member.targetOrSelf(ctx.model) targetShape?.hasTrait(StreamingTrait::class.java) == false - }.toMutableSet() + }.toSet() val streamingMember = bodyMembers.firstOrNull { it.member.targetOrSelf(ctx.model).hasTrait(StreamingTrait::class.java) } if (streamingMember != null) { writeStreamingMember(streamingMember, initialResponseMembers) @@ -142,41 +142,27 @@ class HttpResponseTraitWithoutHttpPayload( private fun writeInitialResponseMembers(initialResponseMembers: Set) { writer.apply { - write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") - indent() - write("let decoder = JSONDecoder()") - write("do {") - indent() - write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") - - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") - } - - dedent() - write("} catch {") - indent() - write("print(\"Error decoding JSON: \\(error)\")") - - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - write("self.$responseMemberName = nil") + openBlock("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {", "} else {") { + write("let decoder = JSONDecoder()") + openBlock("do {", "} catch {") { + write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") + } + } + write("print(\"Error decoding JSON: \\(error)\")") + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = nil") + } + closeBlock("}") } - - dedent() - write("}") - dedent() - write("} else {") - indent() - initialResponseMembers.forEach { responseMember -> val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) write("self.$responseMemberName = nil") } - - dedent() - write("}") + closeBlock("}") } } From 3b56952cace8e221964d100c5de888a88f23a958 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 5 Oct 2023 16:12:41 -0400 Subject: [PATCH 16/18] revert use of open and close block --- .../HttpResponseTraitWithoutHttpPayload.kt | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index 116a4fd2a..f35bf43b3 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -142,27 +142,35 @@ class HttpResponseTraitWithoutHttpPayload( private fun writeInitialResponseMembers(initialResponseMembers: Set) { writer.apply { - openBlock("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {", "} else {") { - write("let decoder = JSONDecoder()") - openBlock("do {", "} catch {") { - write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") - } - } - write("print(\"Error decoding JSON: \\(error)\")") - initialResponseMembers.forEach { responseMember -> - val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) - write("self.$responseMemberName = nil") - } - closeBlock("}") + write("if let initialDataWithoutHttp = await messageDecoder.awaitInitialResponse() {") + indent() + write("let decoder = JSONDecoder()") + write("do {") + indent() + write("let response = try decoder.decode([String: String].self, from: initialDataWithoutHttp)") + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = response[\"$responseMemberName\"].map { value in KinesisClientTypes.Tag(value: value) }") + } + dedent() + write("} catch {") + indent() + write("print(\"Error decoding JSON: \\(error)\")") + initialResponseMembers.forEach { responseMember -> + val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) + write("self.$responseMemberName = nil") } + dedent() + write("}") + dedent() + write("} else {") + indent() initialResponseMembers.forEach { responseMember -> val responseMemberName = ctx.symbolProvider.toMemberName(responseMember.member) write("self.$responseMemberName = nil") } - closeBlock("}") + dedent() + write("}") } } From 182a6b2bd31babaaef58a1e1202996ad6d1c82cb Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 11 Oct 2023 12:20:10 -0400 Subject: [PATCH 17/18] only need to calculate initialResponseMembers if there is a streaming trait --- .../bindingTraits/HttpResponseTraitWithoutHttpPayload.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt index f35bf43b3..cd76dd545 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/bindingTraits/HttpResponseTraitWithoutHttpPayload.kt @@ -49,12 +49,12 @@ class HttpResponseTraitWithoutHttpPayload( val bodyMembersWithoutQueryTrait = bodyMembers .filter { !it.member.hasTrait(HttpQueryTrait::class.java) } .toMutableSet() - val initialResponseMembers = bodyMembers.filter { - val targetShape = it.member.targetOrSelf(ctx.model) - targetShape?.hasTrait(StreamingTrait::class.java) == false - }.toSet() val streamingMember = bodyMembers.firstOrNull { it.member.targetOrSelf(ctx.model).hasTrait(StreamingTrait::class.java) } if (streamingMember != null) { + val initialResponseMembers = bodyMembers.filter { + val targetShape = it.member.targetOrSelf(ctx.model) + targetShape?.hasTrait(StreamingTrait::class.java) == false + }.toSet() writeStreamingMember(streamingMember, initialResponseMembers) } else if (bodyMembersWithoutQueryTrait.isNotEmpty()) { writeNonStreamingMembers(bodyMembersWithoutQueryTrait) From 605f69ab2e10605f360b3d6a1108fa68b52a5d33 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 11 Oct 2023 15:36:25 -0400 Subject: [PATCH 18/18] fix failing test --- .../src/test/kotlin/EventStreamsInitialResponseTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt index fd71f66fb..ad10072bd 100644 --- a/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/EventStreamsInitialResponseTests.kt @@ -18,12 +18,12 @@ class EventStreamsInitialResponseTests { ) val contents = getFileContents( context.manifest, - "/InitialMessageEventStreams/models/TestStreamOperationWithInitialRequestResponseOutputResponse+HttpResponseBinding.swift" + "/InitialMessageEventStreams/models/TestStreamOperationWithInitialRequestResponseOutput+HttpResponseBinding.swift" ) contents.shouldSyntacticSanityCheck() contents.shouldContainOnlyOnce( """ - extension TestStreamOperationWithInitialRequestResponseOutputResponse: ClientRuntime.HttpResponseBinding { + extension TestStreamOperationWithInitialRequestResponseOutput: ClientRuntime.HttpResponseBinding { public init(httpResponse: ClientRuntime.HttpResponse, decoder: ClientRuntime.ResponseDecoder? = nil) async throws { if case let .stream(stream) = httpResponse.body, let responseDecoder = decoder { let messageDecoder: ClientRuntime.MessageDecoder? = nil