Skip to content

Commit 82ad5a1

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 1ea876c commit 82ad5a1

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
@@ -581,7 +581,7 @@ private void generateOperationDeserializer(
581581

582582
// Write out the error deserialization dispatcher.
583583
Set<StructureShape> errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher(
584-
context, operation, responseType, this::writeErrorCodeParser);
584+
context, operation, responseType, this::writeErrorCodeParser, this.isErrorCodeInBody());
585585
deserializingErrorShapes.addAll(errorShapes);
586586
}
587587

@@ -593,11 +593,13 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
593593
Symbol errorSymbol = symbolProvider.toSymbol(error);
594594
String errorDeserMethodName = ProtocolGenerator.getDeserFunctionName(errorSymbol,
595595
context.getProtocolName()) + "Response";
596+
boolean isBodyParsed = this.isErrorCodeInBody();
596597

597598
writer.openBlock("const $L = async (\n"
598-
+ " output: any,\n"
599+
+ " $L: any,\n"
599600
+ " context: __SerdeContext\n"
600-
+ "): Promise<$T> => {", "};", errorDeserMethodName, errorSymbol, () -> {
601+
+ "): Promise<$T> => {", "};",
602+
errorDeserMethodName, isBodyParsed ? "parsedOutput" : "output", errorSymbol, () -> {
601603
writer.openBlock("const contents: $T = {", "};", errorSymbol, () -> {
602604
writer.write("__type: $S,", error.getId().getName());
603605
writer.write("$$fault: $S,", error.getTrait(ErrorTrait.class).get().getValue());
@@ -608,7 +610,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
608610
});
609611

610612
readHeaders(context, error, bindingIndex);
611-
List<HttpBinding> documentBindings = readResponseBody(context, error, bindingIndex);
613+
List<HttpBinding> documentBindings = readErrorResponseBody(context, error, bindingIndex, isBodyParsed);
612614
// Track all shapes bound to the document so their deserializers may be generated.
613615
documentBindings.forEach(binding -> {
614616
Shape target = model.expectShape(binding.getMember().getTarget());
@@ -620,6 +622,23 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
620622
writer.write("");
621623
}
622624

625+
private List<HttpBinding> readErrorResponseBody(
626+
GenerationContext context,
627+
Shape error,
628+
HttpBindingIndex bindingIndex,
629+
boolean isBodyParsed
630+
) {
631+
TypeScriptWriter writer = context.getWriter();
632+
if (isBodyParsed) {
633+
// Body is already parsed in error dispatcher, simply assign body to data.
634+
writer.write("const data: any = output.body;");
635+
return ListUtils.of();
636+
} else {
637+
// Deserialize response body just like in normal response.
638+
return readResponseBody(context, error, bindingIndex);
639+
}
640+
}
641+
623642
private void readHeaders(
624643
GenerationContext context,
625644
Shape operationOrError,
@@ -677,6 +696,7 @@ private List<HttpBinding> readResponseBody(
677696
List<HttpBinding> documentBindings = bindingIndex.getResponseBindings(operationOrError, Location.DOCUMENT);
678697
documentBindings.sort(Comparator.comparing(HttpBinding::getMemberName));
679698
List<HttpBinding> payloadBindings = bindingIndex.getResponseBindings(operationOrError, Location.PAYLOAD);
699+
680700
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
681701
StructureShape operationOutputOrError = operationOrError.asStructureShape()
682702
.orElseGet(() -> operationIndex.getOutput(operationOrError).orElse(null));
@@ -895,6 +915,14 @@ private String getNumberOutputParam(Location bindingType, String dataSource, Sha
895915
*/
896916
protected abstract void writeErrorCodeParser(GenerationContext context);
897917

918+
/**
919+
* A boolean indicates whether body is collected and parsed in error code parser.
920+
* If so, each error shape deserializer should not parse body again.
921+
*
922+
* @return returns whether the error code exists in response body
923+
*/
924+
protected abstract boolean isErrorCodeInBody();
925+
898926
/**
899927
* Writes the code needed to deserialize the output document of a response.
900928
*

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)