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 Diff line number Diff line
@@ -641,10 +641,8 @@ class ServerProtocolTestGenerator(
            FailingTest(RestJson, "RestJsonHttpWithHeadersButNoPayload", TestType.Request),
            FailingTest(RestJson, "RestJsonInputAndOutputWithQuotedStringHeaders", TestType.Response),

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

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

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

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

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

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

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

    override fun MemberShape.isFlattened(): Boolean = true

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

Loading