Skip to content

Commit 882df42

Browse files
committed
Update HTTP binding protocol generation
This commit updates the support for aws.rest-json-1.1 to build atop the refactor of protocol generation done in smithy-typescript. It also introduces support for the aws.rest-json-1.0 protocol. In doing so, general components were built atop the abstractions provided. The aws.rest-json-1.* set of protocols serialize BigInteger and BigDecimal shapes as JSON numbers on the wire, so an implementation of the DocumentMemberSerVisitor was created. Implementations of the [DocumentShape[Deser|Ser]Visitor classes are also included to support serde of shapes in request and response bodies. A rest-json implementation of the HttpBindingProtocolGenerator has been created to combine the above aspects and handling of document bodies.
1 parent 3e0ff2c commit 882df42

File tree

7 files changed

+534
-313
lines changed

7 files changed

+534
-313
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ public class AddProtocols implements TypeScriptIntegration {
2727

2828
@Override
2929
public List<ProtocolGenerator> getProtocolGenerators() {
30-
return ListUtils.of(new AwsRestJson1_1());
30+
return ListUtils.of(new AwsRestJson1_0(), new AwsRestJson1_1());
3131
}
3232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.aws.typescript.codegen;
17+
18+
/**
19+
* Handles generating the aws.rest-json-1.0 protocol for services.
20+
*
21+
* @inheritDoc
22+
*
23+
* @see RestJsonDocumentBodyGenerator
24+
*/
25+
public final class AwsRestJson1_0 extends RestJsonDocumentBodyGenerator {
26+
27+
@Override
28+
public String getName() {
29+
return "aws.rest-json-1.0";
30+
}
31+
32+
@Override
33+
protected String getDocumentContentType() {
34+
return "application/x-amz-json-1.0";
35+
}
36+
}

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

Lines changed: 8 additions & 312 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,14 @@
1515

1616
package software.amazon.smithy.aws.typescript.codegen;
1717

18-
import java.util.List;
19-
import java.util.Map;
20-
import java.util.TreeMap;
21-
import software.amazon.smithy.codegen.core.Symbol;
22-
import software.amazon.smithy.codegen.core.SymbolProvider;
23-
import software.amazon.smithy.model.knowledge.HttpBinding;
24-
import software.amazon.smithy.model.knowledge.HttpBinding.Location;
25-
import software.amazon.smithy.model.shapes.CollectionShape;
26-
import software.amazon.smithy.model.shapes.MapShape;
27-
import software.amazon.smithy.model.shapes.MemberShape;
28-
import software.amazon.smithy.model.shapes.OperationShape;
29-
import software.amazon.smithy.model.shapes.Shape;
30-
import software.amazon.smithy.model.shapes.ShapeId;
31-
import software.amazon.smithy.model.shapes.ShapeIndex;
32-
import software.amazon.smithy.model.shapes.StructureShape;
33-
import software.amazon.smithy.model.shapes.UnionShape;
34-
import software.amazon.smithy.model.traits.JsonNameTrait;
35-
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
36-
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
37-
import software.amazon.smithy.typescript.codegen.integration.HttpBindingProtocolGenerator;
38-
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator;
39-
40-
public class AwsRestJson1_1 extends HttpBindingProtocolGenerator {
18+
/**
19+
* Handles generating the aws.rest-json-1.1 protocol for services.
20+
*
21+
* @inheritDoc
22+
*
23+
* @see RestJsonDocumentBodyGenerator
24+
*/
25+
public final class AwsRestJson1_1 extends RestJsonDocumentBodyGenerator {
4126

4227
@Override
4328
public String getName() {
@@ -48,293 +33,4 @@ public String getName() {
4833
protected String getDocumentContentType() {
4934
return "application/x-amz-json-1.1";
5035
}
51-
52-
@Override
53-
protected Format getDocumentTimestampFormat() {
54-
return Format.EPOCH_SECONDS;
55-
}
56-
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-
74-
@Override
75-
protected void serializeDocument(
76-
GenerationContext context,
77-
OperationShape operation,
78-
List<HttpBinding> documentBindings
79-
) {
80-
SymbolProvider symbolProvider = context.getSymbolProvider();
81-
TypeScriptWriter writer = context.getWriter();
82-
83-
writer.write("let bodyParams: any = {};");
84-
for (HttpBinding binding : documentBindings) {
85-
// The name of the member to get from the input shape.
86-
String memberName = symbolProvider.toMemberName(binding.getMember());
87-
// Use the jsonName trait value if present, otherwise use the member name.
88-
String locationName = binding.getMember().getTrait(JsonNameTrait.class)
89-
.map(JsonNameTrait::getValue)
90-
.orElseGet(binding::getLocationName);
91-
writeDocumentStructureMemberSerialization(context, memberName, locationName, binding.getMember());
92-
}
93-
94-
writer.write("body = JSON.stringify(bodyParams);");
95-
}
96-
97-
@Override
98-
protected void serializeDocumentStructure(GenerationContext context, StructureShape shape) {
99-
TypeScriptWriter writer = context.getWriter();
100-
101-
writer.write("let bodyParams: any = {};");
102-
// Use a TreeMap to sort the members.
103-
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
104-
members.forEach((memberName, memberShape) -> {
105-
// Use the jsonName trait value if present, otherwise use the member name.
106-
String locationName = memberShape.getTrait(JsonNameTrait.class)
107-
.map(JsonNameTrait::getValue)
108-
.orElse(memberName);
109-
writeDocumentStructureMemberSerialization(context, memberName, locationName, memberShape);
110-
});
111-
writer.write("return bodyParams;");
112-
}
113-
114-
private void writeDocumentStructureMemberSerialization(
115-
GenerationContext context,
116-
String memberName,
117-
String locationName,
118-
MemberShape member
119-
) {
120-
TypeScriptWriter writer = context.getWriter();
121-
Shape target = context.getModel().getShapeIndex().getShape(member.getTarget()).get();
122-
123-
// Generate an if statement to set the bodyParam if the member is set.
124-
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> {
125-
writer.write("bodyParams['$L'] = $L;", locationName,
126-
// Dispatch to the input value provider for any additional handling.
127-
getInputValue(context, Location.DOCUMENT, "input." + memberName, member, target));
128-
});
129-
}
130-
131-
@Override
132-
protected void serializeDocumentCollection(GenerationContext context, CollectionShape shape) {
133-
TypeScriptWriter writer = context.getWriter();
134-
Shape target = context.getModel().getShapeIndex().getShape(shape.getMember().getTarget()).get();
135-
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));
139-
});
140-
}
141-
142-
@Override
143-
protected void serializeDocumentMap(GenerationContext context, MapShape shape) {
144-
TypeScriptWriter writer = context.getWriter();
145-
Shape target = context.getModel().getShapeIndex().getShape(shape.getValue().getTarget()).get();
146-
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));
154-
});
155-
writer.write("return mapParams;");
156-
}
157-
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-
});
200-
}
201-
}
202-
203-
@Override
204-
protected void writeErrorDeserializationDispatcher(
205-
GenerationContext context,
206-
List<ShapeId> errors,
207-
String unknownErrorNamespace
208-
) {
209-
TypeScriptWriter writer = context.getWriter();
210-
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) {
318-
TypeScriptWriter writer = context.getWriter();
319-
ShapeIndex index = context.getModel().getShapeIndex();
320-
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-
});
335-
});
336-
});
337-
// Or write to the unknown member the element in the output.
338-
writer.write("return { $$unknown: output[Object.keys(output)[0]] };");
339-
}
34036
}

0 commit comments

Comments
 (0)