From 98db68ddbe064a273259261bd060f2132a92e358 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Fri, 30 May 2025 18:09:12 +0200 Subject: [PATCH 1/2] Be tolerant of null bodies in tests A body with a value of None is equivalent to a body with an empty bytes value. This updates the test generator to smooth that out. --- .../codegen/HttpProtocolTestGenerator.java | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java index b31257b87..e30b21e6c 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java @@ -43,6 +43,8 @@ import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; +import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.OutputTrait; import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.TimestampFormatTrait.Format; import software.amazon.smithy.protocoltests.traits.AppliesTo; @@ -399,7 +401,7 @@ private void writeRequestBodyComparison(HttpMessageTestCase testCase, PythonWrit } writer.addDependency(SmithyPythonDependency.SMITHY_CORE); writer.addImport("smithy_core.aio.types", "AsyncBytesReader"); - writer.write("actual_body_content = await AsyncBytesReader(actual.body).read()"); + writer.write("actual_body_content = await AsyncBytesReader(actual.body or b'').read()"); writer.write("expected_body_content = b$S", testCase.getBody().get()); compareMediaBlob(testCase, writer); } @@ -779,10 +781,20 @@ private Void structureShape(StructureShape shape, ObjectNode node) { } private Void structureMemberShapes(StructureShape container, ObjectNode node) { - node.getMembers().forEach((keyNode, valueNode) -> { - var memberShape = container.getMember(keyNode.getValue()) - .orElseThrow(() -> new CodegenException("unknown memberShape: " + keyNode.getValue())); - var targetShape = model.expectShape(memberShape.getTarget()); + for (MemberShape member : container.members()) { + var optionalValueNode = node.getMember(member.getMemberName()); + if (optionalValueNode.isEmpty()) { + if (isStringLikeOutputPayload(container, member)) { + // Massage these outputs to always be present because it will generally be impossible to + // determine the difference between an empty body and a missing body. + optionalValueNode = Optional.of(Node.from("")); + } else { + continue; + } + } + var valueNode = optionalValueNode.get(); + + var targetShape = model.expectShape(member.getTarget()); var formatString = "$L = $C,"; if (targetShape.isDocumentShape()) { @@ -791,12 +803,21 @@ private Void structureMemberShapes(StructureShape container, ObjectNode node) { } writer.write(formatString, - context.symbolProvider().toMemberName(memberShape), + context.symbolProvider().toMemberName(member), (Runnable) () -> valueNode.accept(new ValueNodeVisitor(targetShape))); - }); + + } return null; } + private boolean isStringLikeOutputPayload(StructureShape container, MemberShape member) { + if (container.hasTrait(OutputTrait.class) && member.hasTrait(HttpPayloadTrait.class)) { + var target = model.expectShape(member.getTarget()); + return target.isBlobShape() || target.isStringShape(); + } + return false; + } + private Void mapShape(MapShape shape, ObjectNode node) { writer.openBlock("{", "}", From 41a32c0723c238d1737d363b7a80ef90a80d2227 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Thu, 5 Jun 2025 15:55:03 +0200 Subject: [PATCH 2/2] Always have a string-like payload --- .../RestJsonProtocolGenerator.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java index bd2dc6101..fe8180d0c 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java @@ -360,33 +360,33 @@ protected void deserializePayloadBody( var deserializerSymbol = symbolProvider.toSymbol(target); - writer.write("if body:").indent(); - if (target.isUnionShape()) { deserializerSymbol = deserializerSymbol.expectProperty(SymbolProperties.DESERIALIZER); writer.write(""" - codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) - deserializer = codec.create_deserializer(body) - kwargs[$S] = $T().deserialize(deserializer) + if body: + codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) + deserializer = codec.create_deserializer(body) + kwargs[$S] = $T().deserialize(deserializer) """, memberName, deserializerSymbol); } else if (target.isStringShape()) { - writer.write("kwargs[$S] = body.decode('utf-8')", memberName); + writer.write("kwargs[$S] = body.decode('utf-8') if body else \"\"", memberName); } else if (target.isBlobShape()) { - writer.write("kwargs[$S] = body", memberName); + writer.write("kwargs[$S] = body or b\"\"", memberName); } else if (target.isDocumentShape()) { var schemaSymbol = deserializerSymbol.expectProperty(SymbolProperties.SCHEMA); writer.write(""" - codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) - deserializer = codec.create_deserializer(body) - kwargs[$S] = deserializer.read_document($T) + if body: + codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) + deserializer = codec.create_deserializer(body) + kwargs[$S] = deserializer.read_document($T) """, memberName, schemaSymbol); } else { writer.write(""" - codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) - kwargs[$S] = codec.deserialize(body, $T) + if body: + codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) + kwargs[$S] = codec.deserialize(body, $T) """, memberName, deserializerSymbol); } - writer.dedent(); } @Override