4343import software .amazon .smithy .model .shapes .ShapeId ;
4444import software .amazon .smithy .model .shapes .StructureShape ;
4545import software .amazon .smithy .model .shapes .UnionShape ;
46+ import software .amazon .smithy .model .traits .HttpPayloadTrait ;
47+ import software .amazon .smithy .model .traits .OutputTrait ;
4648import software .amazon .smithy .model .traits .StreamingTrait ;
4749import software .amazon .smithy .model .traits .TimestampFormatTrait .Format ;
4850import software .amazon .smithy .protocoltests .traits .AppliesTo ;
@@ -399,7 +401,7 @@ private void writeRequestBodyComparison(HttpMessageTestCase testCase, PythonWrit
399401 }
400402 writer .addDependency (SmithyPythonDependency .SMITHY_CORE );
401403 writer .addImport ("smithy_core.aio.types" , "AsyncBytesReader" );
402- writer .write ("actual_body_content = await AsyncBytesReader(actual.body).read()" );
404+ writer .write ("actual_body_content = await AsyncBytesReader(actual.body or b'' ).read()" );
403405 writer .write ("expected_body_content = b$S" , testCase .getBody ().get ());
404406 compareMediaBlob (testCase , writer );
405407 }
@@ -779,10 +781,20 @@ private Void structureShape(StructureShape shape, ObjectNode node) {
779781 }
780782
781783 private Void structureMemberShapes (StructureShape container , ObjectNode node ) {
782- node .getMembers ().forEach ((keyNode , valueNode ) -> {
783- var memberShape = container .getMember (keyNode .getValue ())
784- .orElseThrow (() -> new CodegenException ("unknown memberShape: " + keyNode .getValue ()));
785- var targetShape = model .expectShape (memberShape .getTarget ());
784+ for (MemberShape member : container .members ()) {
785+ var optionalValueNode = node .getMember (member .getMemberName ());
786+ if (optionalValueNode .isEmpty ()) {
787+ if (isStringLikeOutputPayload (container , member )) {
788+ // Massage these outputs to always be present because it will generally be impossible to
789+ // determine the difference between an empty body and a missing body.
790+ optionalValueNode = Optional .of (Node .from ("" ));
791+ } else {
792+ continue ;
793+ }
794+ }
795+ var valueNode = optionalValueNode .get ();
796+
797+ var targetShape = model .expectShape (member .getTarget ());
786798
787799 var formatString = "$L = $C," ;
788800 if (targetShape .isDocumentShape ()) {
@@ -791,12 +803,21 @@ private Void structureMemberShapes(StructureShape container, ObjectNode node) {
791803 }
792804
793805 writer .write (formatString ,
794- context .symbolProvider ().toMemberName (memberShape ),
806+ context .symbolProvider ().toMemberName (member ),
795807 (Runnable ) () -> valueNode .accept (new ValueNodeVisitor (targetShape )));
796- });
808+
809+ }
797810 return null ;
798811 }
799812
813+ private boolean isStringLikeOutputPayload (StructureShape container , MemberShape member ) {
814+ if (container .hasTrait (OutputTrait .class ) && member .hasTrait (HttpPayloadTrait .class )) {
815+ var target = model .expectShape (member .getTarget ());
816+ return target .isBlobShape () || target .isStringShape ();
817+ }
818+ return false ;
819+ }
820+
800821 private Void mapShape (MapShape shape , ObjectNode node ) {
801822 writer .openBlock ("{" ,
802823 "}" ,
0 commit comments