diff --git a/aws-runtime/http-client-engine-crt/common/src/aws/sdk/kotlin/runtime/http/engine/crt/RequestUtil.kt b/aws-runtime/http-client-engine-crt/common/src/aws/sdk/kotlin/runtime/http/engine/crt/RequestUtil.kt index 3b71bbbce5f..060a7f7aba6 100644 --- a/aws-runtime/http-client-engine-crt/common/src/aws/sdk/kotlin/runtime/http/engine/crt/RequestUtil.kt +++ b/aws-runtime/http-client-engine-crt/common/src/aws/sdk/kotlin/runtime/http/engine/crt/RequestUtil.kt @@ -42,7 +42,11 @@ internal fun HttpRequest.toCrtRequest(callContext: CoroutineContext): aws.sdk.ko headers.forEach { key, values -> appendAll(key, values) } } - val contentLength = body.contentLength?.toString() ?: headers[CONTENT_LENGTH_HEADER] + val bodyLen = body.contentLength + val contentLength = when { + bodyLen != null -> if (bodyLen > 0) bodyLen.toString() else null + else -> headers[CONTENT_LENGTH_HEADER] + } contentLength?.let { crtHeaders.append(CONTENT_LENGTH_HEADER, it) } return aws.sdk.kotlin.crt.http.HttpRequest(method.name, url.encodedPath, crtHeaders.build(), bodyStream) diff --git a/aws-runtime/http-client-engine-crt/common/test/aws/sdk/kotlin/runtime/http/engine/crt/RequestConversionTest.kt b/aws-runtime/http-client-engine-crt/common/test/aws/sdk/kotlin/runtime/http/engine/crt/RequestConversionTest.kt index 5afdb19c389..8f352fcc3a2 100644 --- a/aws-runtime/http-client-engine-crt/common/test/aws/sdk/kotlin/runtime/http/engine/crt/RequestConversionTest.kt +++ b/aws-runtime/http-client-engine-crt/common/test/aws/sdk/kotlin/runtime/http/engine/crt/RequestConversionTest.kt @@ -91,4 +91,18 @@ class RequestConversionTest { val crtBody = crtRequest.body as ReadChannelBodyStream crtBody.cancel() } + + @Test + fun testEngineDoesNotAddContentLengthHeaderForEmptyBody() { + val request = HttpRequest( + HttpMethod.POST, + Url.parse("https://test.aws.com?foo=bar"), + Headers.Empty, + HttpBody.Empty + ) + + val testContext = EmptyCoroutineContext + Job() + val crtRequest = request.toCrtRequest(testContext) + assertFalse(crtRequest.headers.contains("Content-Length")) + } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt index 9aaffe1ba16..08fca8aa548 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/RestJson1.kt @@ -7,10 +7,17 @@ package aws.sdk.kotlin.codegen.protocols import aws.sdk.kotlin.codegen.protocols.core.AwsHttpBindingProtocolGenerator import aws.sdk.kotlin.codegen.protocols.json.JsonHttpBindingProtocolGenerator import software.amazon.smithy.aws.traits.protocols.RestJson1Trait +import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes +import software.amazon.smithy.kotlin.codegen.core.defaultName +import software.amazon.smithy.kotlin.codegen.core.withBlock import software.amazon.smithy.kotlin.codegen.rendering.protocol.* import software.amazon.smithy.model.Model +import software.amazon.smithy.model.knowledge.HttpBinding +import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.StructureShape /** * Handles generating the aws.protocols#restJson1 protocol for services. @@ -24,4 +31,32 @@ class RestJson1 : JsonHttpBindingProtocolGenerator() { override fun getProtocolHttpBindingResolver(model: Model, serviceShape: ServiceShape): HttpBindingResolver = HttpTraitResolver(model, serviceShape, "application/json") + + override fun renderSerializeHttpBody( + ctx: ProtocolGenerator.GenerationContext, + op: OperationShape, + writer: KotlinWriter + ) { + super.renderSerializeHttpBody(ctx, op, writer) + + val resolver = getProtocolHttpBindingResolver(ctx.model, ctx.service) + if (!resolver.hasHttpBody(op)) return + + // restjson1 has some different semantics and expectations around empty structures bound via @httpPayload trait + // * empty structures get serialized to `{}` + // see: https://github.com/awslabs/smithy/pull/924 + val requestBindings = resolver.requestBindings(op) + val httpPayload = requestBindings.firstOrNull { it.location == HttpBinding.Location.PAYLOAD } + if (httpPayload != null) { + // explicit payload member as the sole payload + val memberName = httpPayload.member.defaultName() + val target = ctx.model.expectShape(httpPayload.member.target) + if (target is StructureShape) { + writer.withBlock("if (input.#L == null) {", "}", memberName) { + addImport(RuntimeTypes.Http.ByteArrayContent) + write("builder.body = #T(#S.encodeToByteArray())", RuntimeTypes.Http.ByteArrayContent, "{}") + } + } + } + } } diff --git a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpBindingProtocolGenerator.kt b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpBindingProtocolGenerator.kt index cb6d09c615f..f7a2ad34f12 100644 --- a/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpBindingProtocolGenerator.kt +++ b/codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/protocols/core/AwsHttpBindingProtocolGenerator.kt @@ -78,13 +78,6 @@ abstract class AwsHttpBindingProtocolGenerator : HttpBindingProtocolGenerator() // FIXME - document type not fully supported yet, see https://github.com/awslabs/smithy-kotlin/issues/123 "PutAndGetInlineDocumentsInput", - // aws-sdk-kotlin#390 - "RestJsonHttpWithHeaderMemberNoModeledBody", - "RestJsonHttpWithNoModeledBody", - "RestJsonHttpWithEmptyBlobPayload", - "RestJsonHttpWithEmptyStructurePayload", - "RestJsonHttpWithHeadersButNoPayload", - // smithy-kotlin#519 "SimpleScalarPropertiesWithWhiteSpace", "SimpleScalarPropertiesPureWhiteSpace", diff --git a/gradle.properties b/gradle.properties index 93451222220..5dc3a656508 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ org.gradle.jvmargs=-Xmx6g -XX:MaxPermSize=6g -XX:MaxMetaspaceSize=1G sdkVersion=0.10.0-SNAPSHOT # codegen -smithyVersion=1.13.0 +smithyVersion=1.13.1 smithyGradleVersion=0.5.3 # smithy-kotlin codegen and runtime are versioned together smithyKotlinVersion=0.7.1-SNAPSHOT