Unverified Commit 3d0b98c1 authored by david-perez's avatar david-perez Committed by GitHub
Browse files

Return empty HTTP response body when restJson1 operation has no output shape (#1208)

According to the protocol tests, here's how a server should behave when
the operation has no output, and when the operation output shape is empty
(has no members):

* Empty output is serialized to `"{}"`; clients should gracefully accept `""`.
* No output is serialized to `""`.

Compare this to how clients should behave when the operation has no
input, and when the operation input shape is empty (has no members):

* Empty input is serialized to `""`; servers should gracefully accept `"{}"`.
* No input is serialized to `""`.

Notice the behavioral difference in the empty case among clients and
servers. Since clients treat both cases the same by serializing `""`,
the "no input" case is reduced to the "empty input" case by transforming
the model and creating synthetic empty input shapes for all operations
that have no input shapes.

However, the server behavior is different. Up until this commit servers
were always serializing `"{}"` in both cases. This commit implements the
correct behavior. It does so by disambiguating both cases by checking if
the operation output shape is actually synthetic, which would mean that
the original model has no operation output. In that case the structured
data serializer now returns `null`, so we end up serializing `""` to the
HTTP response body.

This commit also renames the methods in charge of serializing operation
inputs/outputs from the `StructuredDataSerializerGenerator` interface,
and removes the unused class `OutputBodyTrait` from
`SyntheticOutputTrait.kt`.
parent f34abdaf
Loading
Loading
Loading
Loading
+0 −2
Original line number Original line Diff line number Diff line
@@ -641,10 +641,8 @@ class ServerProtocolTestGenerator(
            FailingTest(RestJson, "RestJsonHttpWithHeadersButNoPayload", TestType.Request),
            FailingTest(RestJson, "RestJsonHttpWithHeadersButNoPayload", TestType.Request),
            FailingTest(RestJson, "RestJsonInputAndOutputWithQuotedStringHeaders", TestType.Response),
            FailingTest(RestJson, "RestJsonInputAndOutputWithQuotedStringHeaders", TestType.Response),


            FailingTest(RestJson, "RestJsonUnitInputAndOutputNoOutput", TestType.Response),
            FailingTest(RestJson, "RestJsonEndpointTrait", TestType.Request),
            FailingTest(RestJson, "RestJsonEndpointTrait", TestType.Request),
            FailingTest(RestJson, "RestJsonEndpointTraitWithHostLabel", TestType.Request),
            FailingTest(RestJson, "RestJsonEndpointTraitWithHostLabel", TestType.Request),
            FailingTest(RestJson, "RestJsonNoInputAndNoOutput", TestType.Response),
            FailingTest(RestJson, "RestJsonStreamingTraitsRequireLengthWithBlob", TestType.Response),
            FailingTest(RestJson, "RestJsonStreamingTraitsRequireLengthWithBlob", TestType.Response),
            FailingTest(RestJson, "RestJsonHttpWithEmptyBlobPayload", TestType.Request),
            FailingTest(RestJson, "RestJsonHttpWithEmptyBlobPayload", TestType.Request),
            FailingTest(RestJson, "RestJsonHttpWithEmptyStructurePayload", TestType.Request),
            FailingTest(RestJson, "RestJsonHttpWithEmptyStructurePayload", TestType.Request),
+2 −2
Original line number Original line Diff line number Diff line
@@ -110,8 +110,8 @@ class AwsJsonSerializerGenerator(
        "SdkBody" to RuntimeType.sdkBody(runtimeConfig),
        "SdkBody" to RuntimeType.sdkBody(runtimeConfig),
    )
    )


    override fun operationSerializer(operationShape: OperationShape): RuntimeType {
    override fun operationInputSerializer(operationShape: OperationShape): RuntimeType {
        var serializer = jsonSerializerGenerator.operationSerializer(operationShape)
        var serializer = jsonSerializerGenerator.operationInputSerializer(operationShape)
        if (serializer == null) {
        if (serializer == null) {
            val inputShape = operationShape.inputShape(codegenContext.model)
            val inputShape = operationShape.inputShape(codegenContext.model)
            val fnName = codegenContext.symbolProvider.serializeFunctionName(operationShape)
            val fnName = codegenContext.symbolProvider.serializeFunctionName(operationShape)
+2 −2
Original line number Original line Diff line number Diff line
@@ -106,7 +106,7 @@ class HttpBoundProtocolPayloadGenerator(


        if (payloadMemberName == null) {
        if (payloadMemberName == null) {
            val serializerGenerator = protocol.structuredDataSerializer(operationShape)
            val serializerGenerator = protocol.structuredDataSerializer(operationShape)
            generateStructureSerializer(writer, self, serializerGenerator.operationSerializer(operationShape))
            generateStructureSerializer(writer, self, serializerGenerator.operationInputSerializer(operationShape))
        } else {
        } else {
            val payloadMember = operationShape.inputShape(model).expectMember(payloadMemberName)
            val payloadMember = operationShape.inputShape(model).expectMember(payloadMemberName)
            generatePayloadMemberSerializer(writer, self, operationShape, payloadMember)
            generatePayloadMemberSerializer(writer, self, operationShape, payloadMember)
@@ -118,7 +118,7 @@ class HttpBoundProtocolPayloadGenerator(


        if (payloadMemberName == null) {
        if (payloadMemberName == null) {
            val serializerGenerator = protocol.structuredDataSerializer(operationShape)
            val serializerGenerator = protocol.structuredDataSerializer(operationShape)
            generateStructureSerializer(writer, self, serializerGenerator.serverOutputSerializer(operationShape))
            generateStructureSerializer(writer, self, serializerGenerator.operationOutputSerializer(operationShape))
        } else {
        } else {
            val payloadMember = operationShape.outputShape(model).expectMember(payloadMemberName)
            val payloadMember = operationShape.outputShape(model).expectMember(payloadMemberName)
            generatePayloadMemberSerializer(writer, self, operationShape, payloadMember)
            generatePayloadMemberSerializer(writer, self, operationShape, payloadMember)
+1 −1
Original line number Original line Diff line number Diff line
@@ -22,7 +22,7 @@ class AwsQuerySerializerGenerator(codegenContext: CodegenContext) : QuerySeriali


    override fun MemberShape.isFlattened(): Boolean = getTrait<XmlFlattenedTrait>() != null
    override fun MemberShape.isFlattened(): Boolean = getTrait<XmlFlattenedTrait>() != null


    override fun serverOutputSerializer(operationShape: OperationShape): RuntimeType? {
    override fun operationOutputSerializer(operationShape: OperationShape): RuntimeType? {
        TODO("Not yet implemented")
        TODO("Not yet implemented")
    }
    }


+1 −1
Original line number Original line Diff line number Diff line
@@ -25,7 +25,7 @@ class Ec2QuerySerializerGenerator(codegenContext: CodegenContext) : QuerySeriali


    override fun MemberShape.isFlattened(): Boolean = true
    override fun MemberShape.isFlattened(): Boolean = true


    override fun serverOutputSerializer(operationShape: OperationShape): RuntimeType? {
    override fun operationOutputSerializer(operationShape: OperationShape): RuntimeType? {
        TODO("Not yet implemented")
        TODO("Not yet implemented")
    }
    }


Loading