Skip to content

Commit 94c21ba

Browse files
authored
Add deserialization support for aws.rest-json-1.1 (#439)
* Add parseBody function generation * Add deserialization support for rest-json-1.1 This commit adds support for deserializing responses, including errors and complex document contents for the aws.rest-json-1.1 protocol. It also includes fixes for serialization functionality found in the process of writing the above updates.
1 parent b95db12 commit 94c21ba

File tree

1 file changed

+211
-42
lines changed
  • codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen

1 file changed

+211
-42
lines changed

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsRestJson1_1.java

Lines changed: 211 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
package software.amazon.smithy.aws.typescript.codegen;
1717

1818
import java.util.List;
19+
import java.util.Map;
20+
import java.util.TreeMap;
21+
import software.amazon.smithy.codegen.core.Symbol;
1922
import software.amazon.smithy.codegen.core.SymbolProvider;
2023
import software.amazon.smithy.model.knowledge.HttpBinding;
2124
import software.amazon.smithy.model.knowledge.HttpBinding.Location;
@@ -24,14 +27,15 @@
2427
import software.amazon.smithy.model.shapes.MemberShape;
2528
import software.amazon.smithy.model.shapes.OperationShape;
2629
import software.amazon.smithy.model.shapes.Shape;
30+
import software.amazon.smithy.model.shapes.ShapeId;
2731
import software.amazon.smithy.model.shapes.ShapeIndex;
28-
import software.amazon.smithy.model.shapes.SimpleShape;
2932
import software.amazon.smithy.model.shapes.StructureShape;
3033
import software.amazon.smithy.model.shapes.UnionShape;
3134
import software.amazon.smithy.model.traits.JsonNameTrait;
3235
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
3336
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
3437
import software.amazon.smithy.typescript.codegen.integration.HttpBindingProtocolGenerator;
38+
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
3539

3640
public class AwsRestJson1_1 extends HttpBindingProtocolGenerator {
3741

@@ -50,6 +54,23 @@ protected Format getDocumentTimestampFormat() {
5054
return Format.EPOCH_SECONDS;
5155
}
5256

57+
@Override
58+
public void generateSharedComponents(GenerationContext context) {
59+
super.generateSharedComponents(context);
60+
61+
TypeScriptWriter writer = context.getWriter();
62+
63+
writer.addImport("SerdeContext", "SerdeContext", "@aws-sdk/types");
64+
writer.openBlock("const parseBody = (streamBody: any, context: SerdeContext): any => {", "};",
65+
() -> {
66+
writer.openBlock("return context.streamCollector(streamBody).then((body: any) => {", "});", () -> {
67+
writer.write("return JSON.parse(context.utf8Encoder(body));");
68+
});
69+
});
70+
71+
writer.write("");
72+
}
73+
5374
@Override
5475
protected void serializeDocument(
5576
GenerationContext context,
@@ -67,8 +88,7 @@ protected void serializeDocument(
6788
String locationName = binding.getMember().getTrait(JsonNameTrait.class)
6889
.map(JsonNameTrait::getValue)
6990
.orElseGet(binding::getLocationName);
70-
writeDocumentStructureMemberSerialization(context, operation,
71-
memberName, locationName, binding.getMember());
91+
writeDocumentStructureMemberSerialization(context, memberName, locationName, binding.getMember());
7292
}
7393

7494
writer.write("body = JSON.stringify(bodyParams);");
@@ -79,19 +99,20 @@ protected void serializeDocumentStructure(GenerationContext context, StructureSh
7999
TypeScriptWriter writer = context.getWriter();
80100

81101
writer.write("let bodyParams: any = {};");
82-
shape.getAllMembers().forEach((memberName, memberShape) -> {
102+
// Use a TreeMap to sort the members.
103+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
104+
members.forEach((memberName, memberShape) -> {
83105
// Use the jsonName trait value if present, otherwise use the member name.
84106
String locationName = memberShape.getTrait(JsonNameTrait.class)
85107
.map(JsonNameTrait::getValue)
86108
.orElse(memberName);
87-
writeDocumentStructureMemberSerialization(context, shape, memberName, locationName, memberShape);
109+
writeDocumentStructureMemberSerialization(context, memberName, locationName, memberShape);
88110
});
89111
writer.write("return bodyParams;");
90112
}
91113

92114
private void writeDocumentStructureMemberSerialization(
93115
GenerationContext context,
94-
Shape container,
95116
String memberName,
96117
String locationName,
97118
MemberShape member
@@ -102,7 +123,8 @@ private void writeDocumentStructureMemberSerialization(
102123
// Generate an if statement to set the bodyParam if the member is set.
103124
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> {
104125
writer.write("bodyParams['$L'] = $L;", locationName,
105-
getInputValue(context, Location.DOCUMENT, container, member, target));
126+
// Dispatch to the input value provider for any additional handling.
127+
getInputValue(context, Location.DOCUMENT, "input." + memberName, member, target));
106128
});
107129
}
108130

@@ -111,10 +133,9 @@ protected void serializeDocumentCollection(GenerationContext context, Collection
111133
TypeScriptWriter writer = context.getWriter();
112134
Shape target = context.getModel().getShapeIndex().getShape(shape.getMember().getTarget()).get();
113135

114-
// Validate we have input, then get the right serialization for the member target.
115-
writer.write("input &&");
116-
writer.openBlock("input.map(entry =>", ");", () -> {
117-
writer.write(getInputValue(context, Location.DOCUMENT, shape, shape.getMember(), target));
136+
// Dispatch to the input value provider for any additional handling.
137+
writer.openBlock("return (input || []).map(entry =>", ");", () -> {
138+
writer.write(getInputValue(context, Location.DOCUMENT, "entry", shape.getMember(), target));
118139
});
119140
}
120141

@@ -123,49 +144,197 @@ protected void serializeDocumentMap(GenerationContext context, MapShape shape) {
123144
TypeScriptWriter writer = context.getWriter();
124145
Shape target = context.getModel().getShapeIndex().getShape(shape.getValue().getTarget()).get();
125146

126-
// Validate we have input, then get the right serialization for the map value.
127-
writer.write("input.name &&");
128-
writer.openBlock("input.value && {", "};", () -> {
129-
writer.write("name: input.name,");
130-
writer.write("value: $L", getInputValue(context, Location.DOCUMENT, shape, shape.getValue(), target));
147+
// Get the right serialization for each entry in the map. Undefined
148+
// inputs won't have this serializer invoked.
149+
writer.write("let mapParams: any = {};");
150+
writer.openBlock("Object.keys(input).forEach(key => {", "});", () -> {
151+
writer.write("mapParams[key] = $L;",
152+
// Dispatch to the input value provider for any additional handling.
153+
getInputValue(context, Location.DOCUMENT, "input[key]", shape.getValue(), target));
131154
});
155+
writer.write("return mapParams;");
132156
}
133157

134-
// TODO Collection cleanup point
135-
// This, and the location it is invoked, can be cleaned up if/when a
136-
// centralized way to check for and/or handle these differences is built.
137-
private boolean isSimpleCollection(ShapeIndex index, Shape shape) {
138-
if (shape instanceof CollectionShape) {
139-
Shape target = index.getShape(((CollectionShape) shape).getMember().getTarget()).get();
140-
if (target instanceof CollectionShape) {
141-
return isSimpleCollection(index, target);
142-
}
143-
return target instanceof SimpleShape;
158+
@Override
159+
protected void serializeDocumentUnion(GenerationContext context, UnionShape shape) {
160+
TypeScriptWriter writer = context.getWriter();
161+
ShapeIndex index = context.getModel().getShapeIndex();
162+
163+
// Visit over the union type, then get the right serialization for the member.
164+
writer.openBlock("return $L.visit(input, {", "});", shape.getId().getName(), () -> {
165+
// Use a TreeMap to sort the members.
166+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
167+
members.forEach((memberName, memberShape) -> {
168+
Shape target = index.getShape(memberShape.getTarget()).get();
169+
// Dispatch to the input value provider for any additional handling.
170+
writer.write("$L: value => $L,", memberName,
171+
getInputValue(context, Location.DOCUMENT, "value", memberShape, target));
172+
});
173+
writer.write("_: value => value");
174+
});
175+
}
176+
177+
@Override
178+
protected void deserializeDocument(
179+
GenerationContext context,
180+
OperationShape operation,
181+
List<HttpBinding> documentBindings
182+
) {
183+
TypeScriptWriter writer = context.getWriter();
184+
SymbolProvider symbolProvider = context.getSymbolProvider();
185+
186+
for (HttpBinding binding : documentBindings) {
187+
Shape target = context.getModel().getShapeIndex().getShape(binding.getMember().getTarget()).get();
188+
// The name of the member to get from the input shape.
189+
String memberName = symbolProvider.toMemberName(binding.getMember());
190+
// Use the jsonName trait value if present, otherwise use the member name.
191+
String locationName = binding.getMember().getTrait(JsonNameTrait.class)
192+
.map(JsonNameTrait::getValue)
193+
.orElseGet(binding::getLocationName);
194+
writer.openBlock("if (data.$L !== undefined) {", "}", locationName, () -> {
195+
writer.write("contents.$L = $L;", memberName,
196+
// Dispatch to the output value provider for any additional handling.
197+
getOutputValue(context, Location.DOCUMENT, "data." + locationName,
198+
binding.getMember(), target));
199+
});
144200
}
145-
return false;
146201
}
147202

148203
@Override
149-
protected void serializeDocumentUnion(GenerationContext context, UnionShape shape) {
204+
protected void writeErrorDeserializationDispatcher(
205+
GenerationContext context,
206+
List<ShapeId> errors,
207+
String unknownErrorNamespace
208+
) {
209+
TypeScriptWriter writer = context.getWriter();
150210
SymbolProvider symbolProvider = context.getSymbolProvider();
211+
212+
writer.write("let errorCode: String = output.headers[\"x-amzn-errortype\"].split(':')[0];");
213+
writer.openBlock("switch (errorCode) {", "}", () -> {
214+
// Generate the case statement for each error, invoking the specific deserializer.
215+
errors.forEach(errorId -> {
216+
Shape error = context.getModel().getShapeIndex().getShape(errorId).get();
217+
Symbol symbol = symbolProvider.toSymbol(error);
218+
String methodName = ProtocolGenerator.getDeserFunctionName(symbol, getName());
219+
writer.openBlock("case $S:\ncase $S:", " break;", errorId.getName(), errorId.toString(), () -> {
220+
// Dispatch to the error deserialization function.
221+
writer.write("response = $L(data, context);", methodName);
222+
});
223+
});
224+
225+
// Build a generic error the best we can for ones we don't know about.
226+
writer.openBlock("default:", "", () -> {
227+
writer.write("errorCode = errorCode || \"UnknownError\";");
228+
writer.openBlock("response = {", "};", () -> {
229+
writer.write("__type: `$L#$${errorCode}`,", unknownErrorNamespace);
230+
writer.write("$$name: errorCode,");
231+
writer.write("$$fault: \"client\",");
232+
});
233+
});
234+
});
235+
}
236+
237+
@Override
238+
protected void deserializeError(GenerationContext context, StructureShape shape) {
239+
// Use a TreeMap to sort the members.
240+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
241+
members.forEach((memberName, memberShape) -> {
242+
// Use the jsonName trait value if present, otherwise use the member name.
243+
String locationName = memberShape.getTrait(JsonNameTrait.class)
244+
.map(JsonNameTrait::getValue)
245+
.orElse(memberName);
246+
writeDocumentStructureMemberDeserialization(context, memberName, locationName, memberShape);
247+
});
248+
}
249+
250+
@Override
251+
protected void deserializeDocumentStructure(GenerationContext context, StructureShape shape) {
252+
TypeScriptWriter writer = context.getWriter();
253+
254+
// Prepare the document contents structure.
255+
writer.openBlock("let contents: any = {", "};", () -> {
256+
writer.write("$$namespace: $S,", shape.getId().getNamespace());
257+
writer.write("$$name: $S", shape.getId().getName());
258+
});
259+
// Use a TreeMap to sort the members.
260+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
261+
members.forEach((memberName, memberShape) -> {
262+
// Use the jsonName trait value if present, otherwise use the member name.
263+
String locationName = memberShape.getTrait(JsonNameTrait.class)
264+
.map(JsonNameTrait::getValue)
265+
.orElse(memberName);
266+
writeDocumentStructureMemberDeserialization(context, memberName, locationName, memberShape);
267+
});
268+
269+
writer.write("return contents;");
270+
}
271+
272+
private void writeDocumentStructureMemberDeserialization(
273+
GenerationContext context,
274+
String memberName,
275+
String locationName,
276+
MemberShape member
277+
) {
278+
TypeScriptWriter writer = context.getWriter();
279+
Shape target = context.getModel().getShapeIndex().getShape(member.getTarget()).get();
280+
281+
// Generate an if statement to set the bodyParam if the member is set.
282+
writer.openBlock("if (output.$L !== undefined) {", "}", locationName, () -> {
283+
writer.write("contents.$L = $L;", memberName,
284+
// Dispatch to the output value provider for any additional handling.
285+
getOutputValue(context, Location.DOCUMENT, "output." + locationName, member, target));
286+
});
287+
}
288+
289+
@Override
290+
protected void deserializeDocumentCollection(GenerationContext context, CollectionShape shape) {
291+
TypeScriptWriter writer = context.getWriter();
292+
Shape target = context.getModel().getShapeIndex().getShape(shape.getMember().getTarget()).get();
293+
294+
// Dispatch to the output value provider for any additional handling.
295+
writer.openBlock("return (output || []).map((entry: any) =>", ");", () -> {
296+
writer.write(getOutputValue(context, Location.DOCUMENT, "entry", shape.getMember(), target));
297+
});
298+
}
299+
300+
@Override
301+
protected void deserializeDocumentMap(GenerationContext context, MapShape shape) {
302+
TypeScriptWriter writer = context.getWriter();
303+
Shape target = context.getModel().getShapeIndex().getShape(shape.getValue().getTarget()).get();
304+
305+
// Get the right serialization for each entry in the map. Undefined
306+
// outputs won't have this deserializer invoked.
307+
writer.write("let mapParams: any = {};");
308+
writer.openBlock("Object.keys(output).forEach(key => {", "});", () -> {
309+
writer.write("mapParams[key] = $L;",
310+
// Dispatch to the output value provider for any additional handling.
311+
getOutputValue(context, Location.DOCUMENT, "output[key]", shape.getValue(), target));
312+
});
313+
writer.write("return mapParams;");
314+
}
315+
316+
@Override
317+
protected void deserializeDocumentUnion(GenerationContext context, UnionShape shape) {
151318
TypeScriptWriter writer = context.getWriter();
152319
ShapeIndex index = context.getModel().getShapeIndex();
153320

154-
// Visit over the union type, then get the right serialization for the member.
155-
writer.openBlock("$L.visit(input, {", "});", shape.getId().getName(), () -> {
156-
shape.getAllMembers().forEach((name, member) -> {
157-
writer.openBlock("$L: value => {", "},", symbolProvider.toMemberName(member), () -> {
158-
Shape target = index.getShape(member.getTarget()).get();
159-
// TODO See collection cleanup note
160-
// Make sure we invoke the other serialization of union members that need it.
161-
if (!(target instanceof SimpleShape) && !isSimpleCollection(index, target)) {
162-
writer.write("$L;", getInputValue(context, Location.DOCUMENT, shape, member, target));
163-
} else {
164-
writer.write("value;");
165-
}
166-
});
321+
// Check for any known union members and return when we find one.
322+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
323+
members.forEach((memberName, memberShape) -> {
324+
Shape target = index.getShape(memberShape.getTarget()).get();
325+
// Use the jsonName trait value if present, otherwise use the member name.
326+
String locationName = memberShape.getTrait(JsonNameTrait.class)
327+
.map(JsonNameTrait::getValue)
328+
.orElse(memberName);
329+
writer.openBlock("if (output.$L !== undefined) {", "}", locationName, () -> {
330+
writer.openBlock("return {", "};", () -> {
331+
// Dispatch to the output value provider for any additional handling.
332+
writer.write("$L: $L", memberName, getOutputValue(context, Location.DOCUMENT,
333+
"output." + locationName, memberShape, target));
334+
});
167335
});
168-
writer.openBlock("_: value => {", "}", () -> writer.write("value;"));
169336
});
337+
// Or write to the unknown member the element in the output.
338+
writer.write("return { $$unknown: output[Object.keys(output)[0]] };");
170339
}
171340
}

0 commit comments

Comments
 (0)