Skip to content

Commit e9226a4

Browse files
committed
Revert "fix response payload and incorrectly parsing error response (#63)"
This reverts commit 44a72fe.
1 parent 44a72fe commit e9226a4

File tree

3 files changed

+41
-128
lines changed

3 files changed

+41
-128
lines changed

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

Lines changed: 25 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ public void generateSharedComponents(GenerationContext context) {
119119
generateDocumentBodyShapeSerializers(context, serializingDocumentShapes);
120120
generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes);
121121
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
122-
HttpProtocolGeneratorUtils.generateCollectBody(context);
123-
HttpProtocolGeneratorUtils.generateCollectBodyString(context);
124122
}
125123

126124
/**
@@ -595,7 +593,7 @@ private void generateOperationDeserializer(
595593

596594
// Write out the error deserialization dispatcher.
597595
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
598-
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody());
596+
context, operation, responseType, this::writeErrorCodeParser);
599597
deserializingErrorShapes.addAll(errorShapes);
600598
}
601599

@@ -607,13 +605,11 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
607605
Symbol errorSymbol = symbolProvider.toSymbol(error);
608606
String errorDeserMethodName = ProtocolGenerator.getDeserFunctionName(errorSymbol,
609607
context.getProtocolName()) + "Response";
610-
boolean isBodyParsed = this.isErrorCodeInBody();
611608

612609
writer.openBlock("const $L = async (\n"
613-
+ " $L: any,\n"
610+
+ " output: any,\n"
614611
+ " context: __SerdeContext\n"
615-
+ "): Promise<$T> => {", "};",
616-
errorDeserMethodName, isBodyParsed ? "parsedOutput" : "output", errorSymbol, () -> {
612+
+ "): Promise<$T> => {", "};", errorDeserMethodName, errorSymbol, () -> {
617613
writer.openBlock("const contents: $T = {", "};", errorSymbol, () -> {
618614
writer.write("__type: $S,", error.getId().getName());
619615
writer.write("$$fault: $S,", error.getTrait(ErrorTrait.class).get().getValue());
@@ -624,7 +620,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
624620
});
625621

626622
readHeaders(context, error, bindingIndex);
627-
List<HttpBinding> documentBindings = readErrorResponseBody(context, error, bindingIndex, isBodyParsed);
623+
List<HttpBinding> documentBindings = readResponseBody(context, error, bindingIndex);
628624
// Track all shapes bound to the document so their deserializers may be generated.
629625
documentBindings.forEach(binding -> {
630626
Shape target = model.expectShape(binding.getMember().getTarget());
@@ -636,23 +632,6 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
636632
writer.write("");
637633
}
638634

639-
private List<HttpBinding> readErrorResponseBody(
640-
GenerationContext context,
641-
Shape error,
642-
HttpBindingIndex bindingIndex,
643-
boolean isBodyParsed
644-
) {
645-
TypeScriptWriter writer = context.getWriter();
646-
if (isBodyParsed) {
647-
// Body is already parsed in error dispatcher, simply assign body to data.
648-
writer.write("const data: any = output.body;");
649-
return ListUtils.of();
650-
} else {
651-
// Deserialize response body just like in normal response.
652-
return readResponseBody(context, error, bindingIndex);
653-
}
654-
}
655-
656635
private void readHeaders(
657636
GenerationContext context,
658637
Shape operationOrError,
@@ -711,44 +690,42 @@ private List<HttpBinding> readResponseBody(
711690
documentBindings.sort(Comparator.comparing(HttpBinding::getMemberName));
712691
List<HttpBinding> payloadBindings = bindingIndex.getResponseBindings(operationOrError, Location.PAYLOAD);
713692

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-
722693
if (!documentBindings.isEmpty()) {
723-
// If response has document binding, the body can be parsed to JavaScript object.
724-
writer.write("const data: any = await parseBody(output.body, context);");
694+
readReponseBodyData(context, operationOrError);
725695
deserializeOutputDocument(context, operationOrError, documentBindings);
726696
return documentBindings;
727697
}
728698
if (!payloadBindings.isEmpty()) {
699+
readReponseBodyData(context, operationOrError);
729700
// There can only be one payload binding.
730701
HttpBinding binding = payloadBindings.get(0);
731702
Shape target = context.getModel().expectShape(binding.getMember().getTarget());
732-
if (hasStreamingComponent) {
733-
// If payload is streaming, return raw low-level stream directly.
734-
writer.write("const data: any = output.body;");
735-
} else if (target instanceof BlobShape) {
736-
// If payload is blob, only need to collect stream to binary data(Uint8Array).
737-
writer.write("const data: any = await collectBody(output.body, context);");
738-
} else if (target instanceof StructureShape || target instanceof UnionShape) {
739-
// If body is Structure or Union, they we need to parse the string into JavaScript object.
740-
writer.write("const data: any = await parseBody(output.body, context);");
741-
} else {
742-
// If payload is string, we need to collect body and encode binary to string.
743-
writer.write("const data: any = await collectBodyString(output.body, context);");
744-
}
745703
writer.write("contents.$L = $L;", binding.getMemberName(), getOutputValue(context,
746704
Location.PAYLOAD, "data", binding.getMember(), target));
747705
return payloadBindings;
748706
}
749707
return ListUtils.of();
750708
}
751709

710+
private void readReponseBodyData(GenerationContext context, Shape operationOrError) {
711+
TypeScriptWriter writer = context.getWriter();
712+
// Prepare response body for deserializing.
713+
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
714+
StructureShape operationOutputOrError = operationOrError.asStructureShape()
715+
.orElseGet(() -> operationIndex.getOutput(operationOrError).orElse(null));
716+
boolean hasStreamingComponent = Optional.ofNullable(operationOutputOrError)
717+
.map(structure -> structure.getAllMembers().values().stream()
718+
.anyMatch(memberShape -> memberShape.hasTrait(StreamingTrait.class)))
719+
.orElse(false);
720+
if (hasStreamingComponent) {
721+
// For operations with streaming output or errors with streaming body we keep the body intact.
722+
writer.write("const data: any = output.body;");
723+
} else {
724+
// Otherwise, we collect the response body to structured object with parseBody().
725+
writer.write("const data: any = await parseBody(output.body, context);");
726+
}
727+
}
728+
752729
/**
753730
* Given context and a source of data, generate an output value provider for the
754731
* shape. This may use native types (like generating a Date for timestamps,)
@@ -929,14 +906,6 @@ private String getNumberOutputParam(Location bindingType, String dataSource, Sha
929906
*/
930907
protected abstract void writeErrorCodeParser(GenerationContext context);
931908

932-
/**
933-
* A boolean indicates whether body is collected and parsed in error code parser.
934-
* If so, each error shape deserializer should not parse body again.
935-
*
936-
* @return returns whether the error code exists in response body
937-
*/
938-
protected abstract boolean isErrorCodeInBody();
939-
940909
/**
941910
* Writes the code needed to deserialize the output document of a response.
942911
*

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

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

111-
/**
112-
* Writes a response body stream collector. This function converts the low-level response body stream to
113-
* Uint8Array binary data.
114-
*
115-
* @param context The generation context.
116-
*/
117-
static void generateCollectBody(GenerationContext context) {
118-
TypeScriptWriter writer = context.getWriter();
119-
120-
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
121-
writer.write("// Collect low-level response body stream to Uint8Array.");
122-
writer.openBlock("const collectBody = (streamBody: any, context: __SerdeContext): Promise<Uint8Array> => {",
123-
"};", () -> {
124-
writer.write("return context.streamCollector(streamBody) || new Uint8Array();");
125-
});
126-
127-
writer.write("");
128-
}
129-
130-
/**
131-
* Writes a function converting the low-level response body stream to utf-8 encoded string. It depends on
132-
* response body stream collector{@link #generateCollectBody(GenerationContext)}.
133-
*
134-
* @param context The generation context
135-
*/
136-
static void generateCollectBodyString(GenerationContext context) {
137-
TypeScriptWriter writer = context.getWriter();
138-
139-
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
140-
writer.write("// Encode Uint8Array data into string with utf-8.");
141-
writer.openBlock("const collectBodyString = (streamBody: any, context: __SerdeContext): Promise<string> => {",
142-
"};", () -> {
143-
writer.write("return collectBody(streamBody, context).then(body => context.utf8Encoder(body));");
144-
});
145-
146-
writer.write("");
147-
}
148-
149111
/**
150112
* Writes a function used to dispatch to the proper error deserializer
151113
* for each error that the operation can return. The generated function
@@ -156,15 +118,13 @@ static void generateCollectBodyString(GenerationContext context) {
156118
* @param operation The operation to generate for.
157119
* @param responseType The response type for the HTTP protocol.
158120
* @param errorCodeGenerator A consumer
159-
* @param shouldParseErrorBody Flag indicating whether need to parse response body
160121
* @return A set of all error structure shapes for the operation that were dispatched to.
161122
*/
162123
static Set<StructureShape> generateErrorDispatcher(
163124
GenerationContext context,
164125
OperationShape operation,
165126
SymbolReference responseType,
166-
Consumer<GenerationContext> errorCodeGenerator,
167-
boolean shouldParseErrorBody
127+
Consumer<GenerationContext> errorCodeGenerator
168128
) {
169129
TypeScriptWriter writer = context.getWriter();
170130
SymbolProvider symbolProvider = context.getSymbolProvider();
@@ -178,15 +138,15 @@ static Set<StructureShape> generateErrorDispatcher(
178138
+ " output: $T,\n"
179139
+ " context: __SerdeContext,\n"
180140
+ "): Promise<$T> {", "}", errorMethodName, responseType, outputType, () -> {
181-
// Prepare error response for parsing error code. If error code needs to be parsed from response body
182-
// then we collect body and parse it to JS object, otherwise leave the response body as is.
183-
writer.openBlock(
184-
"const $L: any = {", "};", shouldParseErrorBody ? "parsedOutput" : "errorOutput",
185-
() -> {
186-
writer.write("...output,");
187-
writer.write("body: $L,",
188-
shouldParseErrorBody ? "await parseBody(output.body, context)" : "output.body");
189-
});
141+
writer.write("const data: any = await parseBody(output.body, context);");
142+
// We only consume the parsedOutput if we're dispatching, so only generate if we will.
143+
if (!operation.getErrors().isEmpty()) {
144+
// Create a holding object since we have already parsed the body, but retain the rest of the output.
145+
writer.openBlock("const parsedOutput: any = {", "};", () -> {
146+
writer.write("...output,");
147+
writer.write("body: data,");
148+
});
149+
}
190150

191151
// Error responses must be at least SmithyException and MetadataBearer implementations.
192152
writer.addImport("SmithyException", "__SmithyException",
@@ -207,19 +167,17 @@ static Set<StructureShape> generateErrorDispatcher(
207167
context.getProtocolName()) + "Response";
208168
writer.openBlock("case $S:\ncase $S:", " break;", errorId.getName(), errorId.toString(), () -> {
209169
// Dispatch to the error deserialization function.
210-
writer.write("response = await $L($L, context);",
211-
errorDeserMethodName, shouldParseErrorBody ? "parsedOutput" : "errorOutput");
170+
writer.write("response = await $L(parsedOutput, context);", errorDeserMethodName);
212171
});
213172
});
214173

215174
// Build a generic error the best we can for ones we don't know about.
216175
writer.write("default:").indent()
217176
.write("errorCode = errorCode || \"UnknownError\";")
218-
.openBlock("response = {", "} as any;", () -> {
177+
.openBlock("response = {", "};", () -> {
219178
writer.write("__type: `$L#$${errorCode}`,", operation.getId().getNamespace());
220179
writer.write("$$fault: \"client\",");
221180
writer.write("$$metadata: deserializeMetadata(output),");
222-
writer.write("message: await collectBodyString(output.body, context)");
223181
}).dedent();
224182
});
225183
writer.write("return Promise.reject(Object.assign(new Error(response.__type), response));");

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

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ private void generateOperationDeserializer(GenerationContext context, OperationS
254254

255255
// Write out the error deserialization dispatcher.
256256
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
257-
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody());
257+
context, operation, responseType, this::writeErrorCodeParser);
258258
deserializingErrorShapes.addAll(errorShapes);
259259
}
260260

@@ -311,13 +311,10 @@ private void readResponseBody(GenerationContext context, OperationShape operatio
311311
* Writes the code that loads an {@code errorCode} String with the content used
312312
* to dispatch errors to specific serializers.
313313
*
314-
* <p>Two variables will be in scope:
314+
* <p>Three variables will be in scope:
315315
* <ul>
316-
* <li>{@code errorOutput} or {@code parsedOutput}: a value of the HttpResponse type.
317-
* {@code errorOutput} is a raw HttpResponse whereas {@code parsedOutput} is a HttpResponse type with
318-
* body parsed to JavaScript object.
319-
* The actual value available is determined by {@link #isErrorCodeInBody}
320-
* </li>
316+
* <li>{@code output}: a value of the HttpResponse type.</li>
317+
* <li>{@code data}: the contents of the response body.</li>
321318
* <li>{@code context}: the SerdeContext.</li>
322319
* </ul>
323320
*
@@ -331,17 +328,6 @@ private void readResponseBody(GenerationContext context, OperationShape operatio
331328
*/
332329
protected abstract void writeErrorCodeParser(GenerationContext context);
333330

334-
/**
335-
* Indicates whether body is collected and parsed in error dispatcher.
336-
*
337-
* <p>If returns true, {@link #writeErrorCodeParser} will have {@code parsedOutput} in scope
338-
*
339-
* <P>If returns false, {@link #writeErrorCodeParser} will have {@code errorOutput} in scope
340-
*
341-
* @return returns whether the error code exists in response body
342-
*/
343-
protected abstract boolean isErrorCodeInBody();
344-
345331
/**
346332
* Writes the code needed to deserialize the output document of a response.
347333
*

0 commit comments

Comments
 (0)