Skip to content

Commit 15534c7

Browse files
committed
complete event deserializer and event union deserializer
1 parent 95489cd commit 15534c7

File tree

2 files changed

+100
-12
lines changed

2 files changed

+100
-12
lines changed

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

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
import software.amazon.smithy.model.shapes.UnionShape;
5555
import software.amazon.smithy.model.traits.EndpointTrait;
5656
import software.amazon.smithy.model.traits.ErrorTrait;
57+
import software.amazon.smithy.model.traits.EventHeaderTrait;
58+
import software.amazon.smithy.model.traits.EventPayloadTrait;
5759
import software.amazon.smithy.model.traits.EventStreamTrait;
5860
import software.amazon.smithy.model.traits.HttpTrait;
5961
import software.amazon.smithy.model.traits.StreamingTrait;
@@ -74,8 +76,8 @@ public abstract class HttpBindingProtocolGenerator implements ProtocolGenerator
7476
private final Set<Shape> serializingDocumentShapes = new TreeSet<>();
7577
private final Set<Shape> deserializingDocumentShapes = new TreeSet<>();
7678
private final Set<StructureShape> deserializingErrorShapes = new TreeSet<>();
77-
private final Set<Shape> serializeEventShapes = new TreeSet<>();
78-
private final Set<Shape> deserializingEventShapes = new TreeSet<>();
79+
private final Set<StructureShape> serializeEventShapes = new TreeSet<>();
80+
private final Set<StructureShape> deserializingEventShapes = new TreeSet<>();
7981
private final Set<UnionShape> serializeEventUnions = new TreeSet<>();
8082
private final Set<UnionShape> deserializeEventUnions = new TreeSet<>();
8183
private final boolean isErrorCodeInBody;
@@ -134,12 +136,14 @@ public ApplicationProtocol getApplicationProtocol() {
134136
@Override
135137
public void generateSharedComponents(GenerationContext context) {
136138
deserializeEventUnions.forEach(eventUnion -> {
137-
Set<StructureShape> events = HttpProtocolGeneratorUtils.generateSerializingEventUnion(context, eventUnion);
139+
Set<StructureShape> events =
140+
HttpProtocolGeneratorUtils.generateDeserializingEventUnion(context, eventUnion);
138141
deserializingEventShapes.addAll(events);
139142
});
140143
serializeEventUnions.forEach(eventUnion -> {
141144

142145
});
146+
deserializingEventShapes.forEach(event -> generateEventDeserializer(context, event));
143147

144148
deserializingErrorShapes.forEach(error -> generateErrorDeserializer(context, error));
145149
generateDocumentBodyShapeSerializers(context, serializingDocumentShapes);
@@ -618,7 +622,7 @@ protected void serializeInputPayload(
618622
private void collectSerializingEventShapes(GenerationContext context, Shape target) {
619623
if (target instanceof StructureShape) {
620624
// Single-event event stream
621-
this.serializeEventShapes.add(target);
625+
this.serializeEventShapes.add(target.asStructureShape().get());
622626
} else if (target instanceof UnionShape) {
623627
// Multi-event event stream. Save the dispatcher
624628
this.serializeEventUnions.add(target.asUnionShape().get());
@@ -844,7 +848,8 @@ private List<HttpBinding> readResponsePayload(
844848
eventStreamDeserializerWrapper(context, binding.getMember(), target);
845849
writer.write("").dedent();
846850
writer.write("contents.$L = data;", binding.getMemberName());
847-
return payloadBindings;
851+
//Not to generate non-eventstream payload shape again
852+
return ListUtils.of();
848853
} else if (hasStreamingComponent) {
849854
// If payload is streaming, return raw low-level stream directly.
850855
writer.write("const data: any = output.body;");
@@ -890,7 +895,7 @@ private void eventStreamDeserializerWrapper(GenerationContext context, MemberSha
890895
});
891896
if (target instanceof StructureShape) {
892897
// Single-event event stream
893-
this.deserializingEventShapes.add(target);
898+
this.deserializingEventShapes.add(target.asStructureShape().get());
894899
writer.write("return $L;",
895900
getOutputValue(context, Location.PAYLOAD, "eventMessage", member, target));
896901
} else if (target instanceof UnionShape) {
@@ -899,12 +904,90 @@ private void eventStreamDeserializerWrapper(GenerationContext context, MemberSha
899904
Symbol symbol = context.getSymbolProvider().toSymbol(target);
900905
String deserFunction = ProtocolGenerator.getDeserFunctionName(symbol, context.getProtocolName())
901906
+ "_event(parsedEvent, context)";
902-
writer.write("return $L;", deserFunction);
907+
writer.write("return await $L;", deserFunction);
903908
}
904909
});
905910
});
906911
}
907912

913+
private void generateEventDeserializer(
914+
GenerationContext context,
915+
StructureShape event
916+
) {
917+
TypeScriptWriter writer = context.getWriter();
918+
SymbolProvider symbolProvider = context.getSymbolProvider();
919+
Symbol symbol = symbolProvider.toSymbol(event);
920+
String methodName = ProtocolGenerator.getDeserFunctionName(symbol, context.getProtocolName()) + "_event";
921+
Model model = context.getModel();
922+
// Handle the general response.
923+
writer.openBlock("const $L = async (\n"
924+
+ " output: any,\n"
925+
+ " context: __SerdeContext\n"
926+
+ "): Promise<$T> => {", "}",
927+
methodName,
928+
symbol, () -> {
929+
writer.openBlock("let contents: $L = {", "} as any;", symbol.getName(), () -> {
930+
if (!event.getAllMembers().values().isEmpty()) {
931+
writer.write("__type: $S,", event.getId().getName());
932+
}
933+
});
934+
List<MemberShape> payloadMembers = event.getAllMembers().values().stream()
935+
.filter(member -> member.hasTrait(EventPayloadTrait.class)).collect(Collectors.toList());
936+
List<MemberShape> headerMembers = event.getAllMembers().values().stream()
937+
.filter(member -> member.hasTrait(EventHeaderTrait.class)).collect(Collectors.toList());
938+
List<MemberShape> documentMembers = event.getAllMembers().values().stream()
939+
.filter(member -> !member.hasTrait(EventHeaderTrait.class)
940+
&& !member.hasTrait(EventPayloadTrait.class))
941+
.collect(Collectors.toList());
942+
if (!payloadMembers.isEmpty()) {
943+
//if EventPayload trait exists, all other traits are EventHeader
944+
headerMembers.addAll(documentMembers);
945+
documentMembers = ListUtils.of();
946+
}
947+
for (MemberShape headerMember : headerMembers) {
948+
String memberName = headerMember.getMemberName();
949+
writer.openBlock("if (output.headers[$S] !== undefined) {", "}", memberName, () -> {
950+
Shape target = model.expectShape(headerMember.getTarget());
951+
String headerValue = getOutputValue(context, Location.HEADER,
952+
"output.headers['" + memberName + "']", headerMember, target);
953+
writer.write("contents.$L = $L;", memberName, headerValue);
954+
});
955+
}
956+
if (!documentMembers.isEmpty()) {
957+
// If response has document binding, the body can be parsed to JavaScript object.
958+
writer.write("const data: any = await parseBody(output.body, context);");
959+
// Deser the event document with the original shape deser function
960+
writer.openBlock("contents = {", "} as any", () -> {
961+
writer.write("...contents,");
962+
writer.write("...$L(data, context)",
963+
ProtocolGenerator.getDeserFunctionName(symbol, context.getProtocolName()));
964+
});
965+
//need original structure shape deserializer to deserialize event body.
966+
deserializingDocumentShapes.add(event);
967+
}
968+
if (!payloadMembers.isEmpty()) {
969+
//There's only one event payload member
970+
MemberShape payloadMember = payloadMembers.get(0);
971+
Shape payloadTarget = model.expectShape(payloadMember.getTarget());
972+
if (payloadTarget instanceof BlobShape) {
973+
// If event payload is a blob, only need to collect stream to binary data(Uint8Array).
974+
writer.write("const data: any = output.body");
975+
} else if (payloadTarget instanceof StructureShape || payloadTarget instanceof UnionShape) {
976+
// If body is Structure or Union, they we need to parse the string into JavaScript object.
977+
writer.write("const data: any = await parseBody(output.body, context);");
978+
} else if (payloadTarget instanceof StringShape) {
979+
// If payload is string, we need to collect body and encode binary to string.
980+
writer.write("const data: any = await collectBodyString(output.body, context);");
981+
} else {
982+
throw new CodegenException(String.format("Unexpected shape type bound to payload: `%s`",
983+
payloadTarget.getType()));
984+
}
985+
writer.write("contents.$L = data", payloadMember.getMemberName());
986+
}
987+
writer.write("return contents;");
988+
});
989+
}
990+
908991
/**
909992
* Given context and a source of data, generate an output value provider for the
910993
* 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: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.TreeSet;
2121
import java.util.function.BiFunction;
2222
import java.util.function.Consumer;
23+
2324
import software.amazon.smithy.codegen.core.CodegenException;
2425
import software.amazon.smithy.codegen.core.Symbol;
2526
import software.amazon.smithy.codegen.core.SymbolProvider;
@@ -128,7 +129,10 @@ static void generateCollectBody(GenerationContext context) {
128129
writer.write("// Collect low-level response body stream to Uint8Array.");
129130
writer.openBlock("const collectBody = (streamBody: any, context: __SerdeContext): Promise<Uint8Array> => {",
130131
"};", () -> {
131-
writer.write("return context.streamCollector(streamBody) || Promise.resolve(new Uint8Array());");
132+
writer.openBlock("if (streamBody instanceof Uint8Array) {", "}", () -> {
133+
writer.write("return Promise.resolve(streamBody);");
134+
});
135+
writer.write("return context.streamCollector(streamBody) || new Uint8Array();");
132136
});
133137

134138
writer.write("");
@@ -289,7 +293,7 @@ static void writeHostPrefix(GenerationContext context, OperationShape operation)
289293
});
290294
}
291295

292-
static Set<StructureShape> generateSerializingEventUnion(
296+
static Set<StructureShape> generateDeserializingEventUnion(
293297
GenerationContext context,
294298
UnionShape events
295299
) {
@@ -300,10 +304,10 @@ static Set<StructureShape> generateSerializingEventUnion(
300304
String methodName = ProtocolGenerator.getDeserFunctionName(symbol, protocolName) + "_event";
301305
Model model = context.getModel();
302306
Set<StructureShape> targets = new TreeSet<>();
303-
writer.openBlock("const $L = (\n"
307+
writer.openBlock("const $L = async (\n"
304308
+ " output: any,\n"
305309
+ " context: __SerdeContext\n"
306-
+ "): $T => {", "}", methodName, symbol, () -> {
310+
+ "): Promise<$T> => {", "}", methodName, symbol, () -> {
307311
events.getAllMembers().forEach((name, member) -> {
308312
Shape target = model.expectShape(member.getTarget());
309313
targets.add(target.asStructureShape().orElseThrow(
@@ -318,10 +322,11 @@ static Set<StructureShape> generateSerializingEventUnion(
318322
String eventDeserMethodName =
319323
ProtocolGenerator.getDeserFunctionName(eventSymbol, protocolName) + "_event";
320324
String statement = eventDeserMethodName + "(output['" + name + "'], context)";
321-
writer.write("$L: $L", name, statement);
325+
writer.write("$L: await $L", name, statement);
322326
});
323327
});
324328
});
329+
writer.write("return {$$unknown: output}");
325330
});
326331
return targets;
327332
}

0 commit comments

Comments
 (0)