Skip to content

Commit c9454c4

Browse files
committed
add support for parsing body in either dispatcher or deser
For some protocols, error type flag exists in error response body, then we need to collect response stream to JS object and parse the error type; For other protocols, error type flag doesn't exist in error response body, then we don't need to collect the response stream in error dispatcher. Instead, we can treat the error like normal response. So that error shape supports the same traits as normal responses like streaming, payload etc. This is done by add a new flag in Protocol generator-- isErrorCodeInBody. When it return true, it means error type flag exists in error response body, then body is parsed in errors dispatcher, and each error deser only need to deal with parsed response body in JS object format. When it returns false, it means error type can be inferred without touching response body, then error deser can access the error response intact.
1 parent 0dbd1d3 commit c9454c4

File tree

3 files changed

+63
-19
lines changed

3 files changed

+63
-19
lines changed

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ private void generateOperationDeserializer(
595595

596596
// Write out the error deserialization dispatcher.
597597
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
598-
context, operation, responseType, this::writeErrorCodeParser);
598+
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody());
599599
deserializingErrorShapes.addAll(errorShapes);
600600
}
601601

@@ -607,11 +607,13 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
607607
Symbol errorSymbol = symbolProvider.toSymbol(error);
608608
String errorDeserMethodName = ProtocolGenerator.getDeserFunctionName(errorSymbol,
609609
context.getProtocolName()) + "Response";
610+
boolean isBodyParsed = this.isErrorCodeInBody();
610611

611612
writer.openBlock("const $L = async (\n"
612-
+ " output: any,\n"
613+
+ " $L: any,\n"
613614
+ " context: __SerdeContext\n"
614-
+ "): Promise<$T> => {", "};", errorDeserMethodName, errorSymbol, () -> {
615+
+ "): Promise<$T> => {", "};",
616+
errorDeserMethodName, isBodyParsed ? "parsedOutput" : "output", errorSymbol, () -> {
615617
writer.openBlock("const contents: $T = {", "};", errorSymbol, () -> {
616618
writer.write("__type: $S,", error.getId().getName());
617619
writer.write("$$fault: $S,", error.getTrait(ErrorTrait.class).get().getValue());
@@ -622,7 +624,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
622624
});
623625

624626
readHeaders(context, error, bindingIndex);
625-
List<HttpBinding> documentBindings = readResponseBody(context, error, bindingIndex);
627+
List<HttpBinding> documentBindings = readErrorResponseBody(context, error, bindingIndex, isBodyParsed);
626628
// Track all shapes bound to the document so their deserializers may be generated.
627629
documentBindings.forEach(binding -> {
628630
Shape target = model.expectShape(binding.getMember().getTarget());
@@ -634,6 +636,23 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
634636
writer.write("");
635637
}
636638

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+
637656
private void readHeaders(
638657
GenerationContext context,
639658
Shape operationOrError,
@@ -691,6 +710,7 @@ private List<HttpBinding> readResponseBody(
691710
List<HttpBinding> documentBindings = bindingIndex.getResponseBindings(operationOrError, Location.DOCUMENT);
692711
documentBindings.sort(Comparator.comparing(HttpBinding::getMemberName));
693712
List<HttpBinding> payloadBindings = bindingIndex.getResponseBindings(operationOrError, Location.PAYLOAD);
713+
694714
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
695715
StructureShape operationOutputOrError = operationOrError.asStructureShape()
696716
.orElseGet(() -> operationIndex.getOutput(operationOrError).orElse(null));
@@ -909,6 +929,14 @@ private String getNumberOutputParam(Location bindingType, String dataSource, Sha
909929
*/
910930
protected abstract void writeErrorCodeParser(GenerationContext context);
911931

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+
912940
/**
913941
* Writes the code needed to deserialize the output document of a response.
914942
*

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,15 @@ static void generateCollectBodyString(GenerationContext context) {
142142
* @param operation The operation to generate for.
143143
* @param responseType The response type for the HTTP protocol.
144144
* @param errorCodeGenerator A consumer
145+
* @param shouldParseErrorBody Flag indicating whether need to parse response body
145146
* @return A set of all error structure shapes for the operation that were dispatched to.
146147
*/
147148
static Set<StructureShape> generateErrorDispatcher(
148149
GenerationContext context,
149150
OperationShape operation,
150151
SymbolReference responseType,
151-
Consumer<GenerationContext> errorCodeGenerator
152+
Consumer<GenerationContext> errorCodeGenerator,
153+
boolean shouldParseErrorBody
152154
) {
153155
TypeScriptWriter writer = context.getWriter();
154156
SymbolProvider symbolProvider = context.getSymbolProvider();
@@ -162,15 +164,14 @@ static Set<StructureShape> generateErrorDispatcher(
162164
+ " output: $T,\n"
163165
+ " context: __SerdeContext,\n"
164166
+ "): Promise<$T> {", "}", errorMethodName, responseType, outputType, () -> {
165-
writer.write("const data: any = await parseBody(output.body, context);");
166-
// We only consume the parsedOutput if we're dispatching, so only generate if we will.
167-
if (!operation.getErrors().isEmpty()) {
168-
// Create a holding object since we have already parsed the body, but retain the rest of the output.
169-
writer.openBlock("const parsedOutput: any = {", "};", () -> {
170-
writer.write("...output,");
171-
writer.write("body: data,");
172-
});
173-
}
167+
// Prepare error response for parsing error code. If error code needs to be parsed from response body
168+
// then we collect body and parse it to JS object, otherwise leave the response body as is.
169+
writer.openBlock(
170+
"const $L: any = {", "};", shouldParseErrorBody ? "parsedOutput" : "errorOutput", () -> {
171+
writer.write("...output,");
172+
writer.write("body: $L,",
173+
shouldParseErrorBody ? "await parseBody(output.body, context)" : "output.body");
174+
});
174175

175176
// Error responses must be at least SmithyException and MetadataBearer implementations.
176177
writer.addImport("SmithyException", "__SmithyException",
@@ -191,7 +192,8 @@ static Set<StructureShape> generateErrorDispatcher(
191192
context.getProtocolName()) + "Response";
192193
writer.openBlock("case $S:\ncase $S:", " break;", errorId.getName(), errorId.toString(), () -> {
193194
// Dispatch to the error deserialization function.
194-
writer.write("response = await $L(parsedOutput, context);", errorDeserMethodName);
195+
writer.write("response = await $L($L, context);",
196+
errorDeserMethodName, shouldParseErrorBody ? "parsedOutput" : "errorOutput");
195197
});
196198
});
197199

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

Lines changed: 18 additions & 4 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);
257+
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody());
258258
deserializingErrorShapes.addAll(errorShapes);
259259
}
260260

@@ -311,10 +311,13 @@ 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>Three variables will be in scope:
314+
* <p>Two variables will be in scope:
315315
* <ul>
316-
* <li>{@code output}: a value of the HttpResponse type.</li>
317-
* <li>{@code data}: the contents of the response body.</li>
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>
318321
* <li>{@code context}: the SerdeContext.</li>
319322
* </ul>
320323
*
@@ -328,6 +331,17 @@ private void readResponseBody(GenerationContext context, OperationShape operatio
328331
*/
329332
protected abstract void writeErrorCodeParser(GenerationContext context);
330333

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+
331345
/**
332346
* Writes the code needed to deserialize the output document of a response.
333347
*

0 commit comments

Comments
 (0)