Skip to content

Commit cc9f66d

Browse files
committed
fix when response payload is not structured string
1 parent e8b9927 commit cc9f66d

File tree

2 files changed

+48
-21
lines changed

2 files changed

+48
-21
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpBindingProtocolGenerator.java

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ public void generateSharedComponents(GenerationContext context) {
120120
generateDocumentBodyShapeSerializers(context, serializingDocumentShapes);
121121
generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes);
122122
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
123+
HttpProtocolGeneratorUtils.generateCollectBody(context);
124+
HttpProtocolGeneratorUtils.generateCollectBodyString(context);
123125
}
124126

125127
/**
@@ -690,43 +692,44 @@ private List<HttpBinding> readResponseBody(
690692
List<HttpBinding> documentBindings = bindingIndex.getResponseBindings(operationOrError, Location.DOCUMENT);
691693
documentBindings.sort(Comparator.comparing(HttpBinding::getMemberName));
692694
List<HttpBinding> payloadBindings = bindingIndex.getResponseBindings(operationOrError, Location.PAYLOAD);
695+
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
696+
StructureShape operationOutputOrError = operationOrError.asStructureShape()
697+
.orElseGet(() -> operationIndex.getOutput(operationOrError).orElse(null));
698+
boolean hasStreamingComponent = Optional.ofNullable(operationOutputOrError)
699+
.map(structure -> structure.getAllMembers().values().stream()
700+
.anyMatch(memberShape -> memberShape.hasTrait(StreamingTrait.class)))
701+
.orElse(false);
693702

694703
if (!documentBindings.isEmpty()) {
695-
readReponseBodyData(context, operationOrError);
704+
writer.write("const data: any = await parseBody(output.body, context);");
696705
deserializeOutputDocument(context, operationOrError, documentBindings);
697706
return documentBindings;
698707
}
699708
if (!payloadBindings.isEmpty()) {
700-
readReponseBodyData(context, operationOrError);
701709
// There can only be one payload binding.
702710
HttpBinding binding = payloadBindings.get(0);
703711
Shape target = context.getModel().expectShape(binding.getMember().getTarget());
712+
if (hasStreamingComponent) {
713+
// If payload is streaming, return raw low-level stream directly.
714+
writer.write("const data: any = output.body;");
715+
} else if (target instanceof BlobShape) {
716+
// If payload is blob, only need to collect stream to binary data(Uint8Array).
717+
writer.write("const data: any = await collectBody(output.body, context);");
718+
} else if (target instanceof CollectionShape || target instanceof StructureShape) {
719+
// If body is Collection or Structure, they we need to parse the string into JavaScript object.
720+
writer.write("const data: any = await parseBody(output.body, context);");
721+
} else {
722+
// If payload is other scalar types(not Collection or Structure), because payload will be values in
723+
// string instead of valid JSON or XML. So we need to collect body and encode binary to string.
724+
writer.write("const data: any = await collectBodyString(output.body, context);");
725+
}
704726
writer.write("contents.$L = $L;", binding.getMemberName(), getOutputValue(context,
705727
Location.PAYLOAD, "data", binding.getMember(), target));
706728
return payloadBindings;
707729
}
708730
return ListUtils.of();
709731
}
710732

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-
730733
/**
731734
* Given context and a source of data, generate an output value provider for the
732735
* shape. This may use native types (like generating a Date for timestamps,)

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpProtocolGeneratorUtils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,30 @@ static void generateMetadataDeserializer(GenerationContext context, SymbolRefere
108108
writer.write("");
109109
}
110110

111+
static void generateCollectBody(GenerationContext context) {
112+
TypeScriptWriter writer = context.getWriter();
113+
114+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
115+
writer.openBlock("const collectBody = (streamBody: any, context: __SerdeContext): Promise<Uint8Array> => {",
116+
"};", () -> {
117+
writer.write("return context.streamCollector(streamBody) || new Uint8Array();");
118+
});
119+
120+
writer.write("");
121+
}
122+
123+
static void generateCollectBodyString(GenerationContext context) {
124+
TypeScriptWriter writer = context.getWriter();
125+
126+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
127+
writer.openBlock("const collectBodyString = (streamBody: any, context: __SerdeContext): Promise<string> => {",
128+
"};", () -> {
129+
writer.write("return collectBody(streamBody, context).then(body => context.utf8Encoder(body));");
130+
});
131+
132+
writer.write("");
133+
}
134+
111135
/**
112136
* Writes a function used to dispatch to the proper error deserializer
113137
* for each error that the operation can return. The generated function

0 commit comments

Comments
 (0)