48
48
import software .amazon .smithy .model .shapes .NumberShape ;
49
49
import software .amazon .smithy .model .shapes .OperationShape ;
50
50
import software .amazon .smithy .model .shapes .Shape ;
51
- import software .amazon .smithy .model .shapes .ShapeType ;
52
51
import software .amazon .smithy .model .shapes .StringShape ;
53
52
import software .amazon .smithy .model .shapes .StructureShape ;
54
53
import software .amazon .smithy .model .shapes .TimestampShape ;
@@ -72,6 +71,17 @@ public abstract class HttpBindingProtocolGenerator implements ProtocolGenerator
72
71
private final Set <Shape > serializingDocumentShapes = new TreeSet <>();
73
72
private final Set <Shape > deserializingDocumentShapes = new TreeSet <>();
74
73
private final Set <StructureShape > deserializingErrorShapes = new TreeSet <>();
74
+ private final boolean isErrorCodeInBody ;
75
+
76
+ /**
77
+ * Creates a Http binding protocol generator.
78
+ *
79
+ * @param isErrorCodeInBody A boolean that indicates if the error code for the implementing protocol is located in
80
+ * the error response body, meaning this generator will parse the body before attempting to load an error code.
81
+ */
82
+ public HttpBindingProtocolGenerator (boolean isErrorCodeInBody ) {
83
+ this .isErrorCodeInBody = isErrorCodeInBody ;
84
+ }
75
85
76
86
@ Override
77
87
public ApplicationProtocol getApplicationProtocol () {
@@ -120,6 +130,8 @@ public void generateSharedComponents(GenerationContext context) {
120
130
generateDocumentBodyShapeSerializers (context , serializingDocumentShapes );
121
131
generateDocumentBodyShapeDeserializers (context , deserializingDocumentShapes );
122
132
HttpProtocolGeneratorUtils .generateMetadataDeserializer (context , getApplicationProtocol ().getResponseType ());
133
+ HttpProtocolGeneratorUtils .generateCollectBody (context );
134
+ HttpProtocolGeneratorUtils .generateCollectBodyString (context );
123
135
}
124
136
125
137
/**
@@ -594,7 +606,7 @@ private void generateOperationDeserializer(
594
606
595
607
// Write out the error deserialization dispatcher.
596
608
Set <StructureShape > errorShapes = HttpProtocolGeneratorUtils .generateErrorDispatcher (
597
- context , operation , responseType , this ::writeErrorCodeParser );
609
+ context , operation , responseType , this ::writeErrorCodeParser , isErrorCodeInBody );
598
610
deserializingErrorShapes .addAll (errorShapes );
599
611
}
600
612
@@ -608,9 +620,10 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
608
620
context .getProtocolName ()) + "Response" ;
609
621
610
622
writer .openBlock ("const $L = async (\n "
611
- + " output : any,\n "
623
+ + " $L : any,\n "
612
624
+ " context: __SerdeContext\n "
613
- + "): Promise<$T> => {" , "};" , errorDeserMethodName , errorSymbol , () -> {
625
+ + "): Promise<$T> => {" , "};" ,
626
+ errorDeserMethodName , isErrorCodeInBody ? "parsedOutput" : "output" , errorSymbol , () -> {
614
627
writer .openBlock ("const contents: $T = {" , "};" , errorSymbol , () -> {
615
628
writer .write ("__type: $S," , error .getId ().getName ());
616
629
writer .write ("$$fault: $S," , error .getTrait (ErrorTrait .class ).get ().getValue ());
@@ -621,7 +634,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
621
634
});
622
635
623
636
readHeaders (context , error , bindingIndex );
624
- List <HttpBinding > documentBindings = readResponseBody (context , error , bindingIndex );
637
+ List <HttpBinding > documentBindings = readErrorResponseBody (context , error , bindingIndex );
625
638
// Track all shapes bound to the document so their deserializers may be generated.
626
639
documentBindings .forEach (binding -> {
627
640
Shape target = model .expectShape (binding .getMember ().getTarget ());
@@ -633,6 +646,24 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
633
646
writer .write ("" );
634
647
}
635
648
649
+ private List <HttpBinding > readErrorResponseBody (
650
+ GenerationContext context ,
651
+ Shape error ,
652
+ HttpBindingIndex bindingIndex
653
+ ) {
654
+ TypeScriptWriter writer = context .getWriter ();
655
+ if (isErrorCodeInBody ) {
656
+ // Body is already parsed in error dispatcher, simply assign body to data.
657
+ writer .write ("const data: any = output.body;" );
658
+ List <HttpBinding > responseBindings = bindingIndex .getResponseBindings (error , Location .DOCUMENT );
659
+ responseBindings .sort (Comparator .comparing (HttpBinding ::getMemberName ));
660
+ return responseBindings ;
661
+ } else {
662
+ // Deserialize response body just like in normal response.
663
+ return readResponseBody (context , error , bindingIndex );
664
+ }
665
+ }
666
+
636
667
private void readHeaders (
637
668
GenerationContext context ,
638
669
Shape operationOrError ,
@@ -691,42 +722,48 @@ private List<HttpBinding> readResponseBody(
691
722
documentBindings .sort (Comparator .comparing (HttpBinding ::getMemberName ));
692
723
List <HttpBinding > payloadBindings = bindingIndex .getResponseBindings (operationOrError , Location .PAYLOAD );
693
724
725
+ // Detect if operation output or error shape contains a streaming member.
726
+ OperationIndex operationIndex = context .getModel ().getKnowledge (OperationIndex .class );
727
+ StructureShape operationOutputOrError = operationOrError .asStructureShape ()
728
+ .orElseGet (() -> operationIndex .getOutput (operationOrError ).orElse (null ));
729
+ boolean hasStreamingComponent = Optional .ofNullable (operationOutputOrError )
730
+ .map (structure -> structure .getAllMembers ().values ().stream ()
731
+ .anyMatch (memberShape -> memberShape .hasTrait (StreamingTrait .class )))
732
+ .orElse (false );
733
+
694
734
if (!documentBindings .isEmpty ()) {
695
- readReponseBodyData (context , operationOrError );
735
+ // If response has document binding, the body can be parsed to JavaScript object.
736
+ writer .write ("const data: any = await parseBody(output.body, context);" );
696
737
deserializeOutputDocument (context , operationOrError , documentBindings );
697
738
return documentBindings ;
698
739
}
699
740
if (!payloadBindings .isEmpty ()) {
700
- readReponseBodyData (context , operationOrError );
701
741
// There can only be one payload binding.
702
742
HttpBinding binding = payloadBindings .get (0 );
703
743
Shape target = context .getModel ().expectShape (binding .getMember ().getTarget ());
744
+ if (hasStreamingComponent ) {
745
+ // If payload is streaming, return raw low-level stream directly.
746
+ writer .write ("const data: any = output.body;" );
747
+ } else if (target instanceof BlobShape ) {
748
+ // If payload is non-streaming blob, only need to collect stream to binary data(Uint8Array).
749
+ writer .write ("const data: any = await collectBody(output.body, context);" );
750
+ } else if (target instanceof StructureShape || target instanceof UnionShape ) {
751
+ // If body is Structure or Union, they we need to parse the string into JavaScript object.
752
+ writer .write ("const data: any = await parseBody(output.body, context);" );
753
+ } else if (target instanceof StringShape ) {
754
+ // If payload is string, we need to collect body and encode binary to string.
755
+ writer .write ("const data: any = await collectBodyString(output.body, context);" );
756
+ } else {
757
+ throw new CodegenException (String .format ("Unexpected shape type bound to payload: `%s`" ,
758
+ target .getType ()));
759
+ }
704
760
writer .write ("contents.$L = $L;" , binding .getMemberName (), getOutputValue (context ,
705
761
Location .PAYLOAD , "data" , binding .getMember (), target ));
706
762
return payloadBindings ;
707
763
}
708
764
return ListUtils .of ();
709
765
}
710
766
711
- private void readReponseBodyData (GenerationContext context , Shape operationOrError ) {
712
- TypeScriptWriter writer = context .getWriter ();
713
- // Prepare response body for deserializing.
714
- OperationIndex operationIndex = context .getModel ().getKnowledge (OperationIndex .class );
715
- StructureShape operationOutputOrError = operationOrError .asStructureShape ()
716
- .orElseGet (() -> operationIndex .getOutput (operationOrError ).orElse (null ));
717
- boolean hasStreamingComponent = Optional .ofNullable (operationOutputOrError )
718
- .map (structure -> structure .getAllMembers ().values ().stream ()
719
- .anyMatch (memberShape -> memberShape .hasTrait (StreamingTrait .class )))
720
- .orElse (false );
721
- if (hasStreamingComponent || operationOrError .getType ().equals (ShapeType .STRUCTURE )) {
722
- // For operations with streaming output or errors with streaming body we keep the body intact.
723
- writer .write ("const data: any = output.body;" );
724
- } else {
725
- // Otherwise, we collect the response body to structured object with parseBody().
726
- writer .write ("const data: any = await parseBody(output.body, context);" );
727
- }
728
- }
729
-
730
767
/**
731
768
* Given context and a source of data, generate an output value provider for the
732
769
* shape. This may use native types (like generating a Date for timestamps,)
@@ -890,10 +927,16 @@ private String getNumberOutputParam(Location bindingType, String dataSource, Sha
890
927
* Writes the code that loads an {@code errorCode} String with the content used
891
928
* to dispatch errors to specific serializers.
892
929
*
893
- * <p>Three variables will be in scope:
930
+ * <p>Two variables will be in scope:
894
931
* <ul>
895
- * <li>{@code output}: a value of the HttpResponse type.</li>
896
- * <li>{@code data}: the contents of the response body.</li>
932
+ * <li>{@code output} or {@code parsedOutput}: a value of the HttpResponse type.
933
+ * <ul>
934
+ * <li>{@code output} is a raw HttpResponse, available when {@code isErrorCodeInBody} is set to
935
+ * {@code false}</li>
936
+ * <li>{@code parsedOutput} is a HttpResponse type with body parsed to JavaScript object, available
937
+ * when {@code isErrorCodeInBody} is set to {@code true}</li>
938
+ * </ul>
939
+ * </li>
897
940
* <li>{@code context}: the SerdeContext.</li>
898
941
* </ul>
899
942
*
0 commit comments