Skip to content

Commit def3531

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 cc9f66d commit def3531

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
@@ -596,7 +596,7 @@ private void generateOperationDeserializer(
596596

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

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

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

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

640+
private List<HttpBinding> readErrorResponseBody(
641+
GenerationContext context,
642+
Shape error,
643+
HttpBindingIndex bindingIndex,
644+
boolean isBodyParsed
645+
) {
646+
TypeScriptWriter writer = context.getWriter();
647+
if (isBodyParsed) {
648+
// Body is already parsed in error dispatcher, simply assign body to data.
649+
writer.write("const data: any = output.body;");
650+
return ListUtils.of();
651+
} else {
652+
// Deserialize response body just like in normal response.
653+
return readResponseBody(context, error, bindingIndex);
654+
}
655+
}
656+
638657
private void readHeaders(
639658
GenerationContext context,
640659
Shape operationOrError,
@@ -692,6 +711,7 @@ private List<HttpBinding> readResponseBody(
692711
List<HttpBinding> documentBindings = bindingIndex.getResponseBindings(operationOrError, Location.DOCUMENT);
693712
documentBindings.sort(Comparator.comparing(HttpBinding::getMemberName));
694713
List<HttpBinding> payloadBindings = bindingIndex.getResponseBindings(operationOrError, Location.PAYLOAD);
714+
695715
OperationIndex operationIndex = context.getModel().getKnowledge(OperationIndex.class);
696716
StructureShape operationOutputOrError = operationOrError.asStructureShape()
697717
.orElseGet(() -> operationIndex.getOutput(operationOrError).orElse(null));
@@ -910,6 +930,14 @@ private String getNumberOutputParam(Location bindingType, String dataSource, Sha
910930
*/
911931
protected abstract void writeErrorCodeParser(GenerationContext context);
912932

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

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)