Skip to content

Commit 4920e03

Browse files
committed
Update HTTP binding protocol generation (#465)
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 to fail generation. 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 52c47ed commit 4920e03

File tree

9 files changed

+600
-313
lines changed

9 files changed

+600
-313
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ build
2727
codegen/build
2828
codegen/sdk-codegen/smithy-build.json
2929
.gradle
30+
*/out/
31+
*/*/out/

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 RestJsonProtocolGenerator
24+
*/
25+
public final class AwsRestJson1_0 extends RestJsonProtocolGenerator {
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
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
import software.amazon.smithy.codegen.core.CodegenException;
19+
import software.amazon.smithy.model.shapes.BigDecimalShape;
20+
import software.amazon.smithy.model.shapes.BigIntegerShape;
21+
import software.amazon.smithy.model.shapes.Shape;
22+
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
23+
import software.amazon.smithy.typescript.codegen.integration.DocumentMemberDeserVisitor;
24+
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
25+
26+
/**
27+
* Overrides the default implementation of BigDecimal and BigInteger shape
28+
* deserialization to throw when encountered in AWS JSON based protocols.
29+
*/
30+
final class JsonMemberDeserVisitor extends DocumentMemberDeserVisitor {
31+
32+
/**
33+
* @inheritDoc
34+
*/
35+
JsonMemberDeserVisitor(
36+
GenerationContext context,
37+
String dataSource,
38+
Format defaultTimestampFormat
39+
) {
40+
super(context, dataSource, defaultTimestampFormat);
41+
}
42+
43+
@Override
44+
public String bigDecimalShape(BigDecimalShape shape) {
45+
// Fail instead of losing precision through Number.
46+
return unsupportedShape(shape);
47+
}
48+
49+
@Override
50+
public String bigIntegerShape(BigIntegerShape shape) {
51+
// Fail instead of losing precision through Number.
52+
return unsupportedShape(shape);
53+
}
54+
55+
private String unsupportedShape(Shape shape) {
56+
throw new CodegenException(String.format("Cannot deserialize shape type %s on protocol, shape: %s.",
57+
shape.getType(), shape.getId()));
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
import software.amazon.smithy.codegen.core.CodegenException;
19+
import software.amazon.smithy.model.shapes.BigDecimalShape;
20+
import software.amazon.smithy.model.shapes.BigIntegerShape;
21+
import software.amazon.smithy.model.shapes.Shape;
22+
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
23+
import software.amazon.smithy.typescript.codegen.integration.DocumentMemberSerVisitor;
24+
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
25+
26+
/**
27+
* Overrides the default implementation of BigDecimal and BigInteger shape
28+
* serialization to throw when encountered in AWS JSON based protocols.
29+
*/
30+
final class JsonMemberSerVisitor extends DocumentMemberSerVisitor {
31+
32+
/**
33+
* @inheritDoc
34+
*/
35+
JsonMemberSerVisitor(
36+
GenerationContext context,
37+
String dataSource,
38+
Format defaultTimestampFormat
39+
) {
40+
super(context, dataSource, defaultTimestampFormat);
41+
}
42+
43+
@Override
44+
public String bigDecimalShape(BigDecimalShape shape) {
45+
// Fail instead of losing precision through Number.
46+
return unsupportedShape(shape);
47+
}
48+
49+
@Override
50+
public String bigIntegerShape(BigIntegerShape shape) {
51+
// Fail instead of losing precision through Number.
52+
return unsupportedShape(shape);
53+
}
54+
55+
private String unsupportedShape(Shape shape) {
56+
throw new CodegenException(String.format("Cannot deserialize shape type %s on protocol, shape: %s.",
57+
shape.getType(), shape.getId()));
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
import java.util.Map;
19+
import java.util.TreeMap;
20+
import software.amazon.smithy.model.shapes.CollectionShape;
21+
import software.amazon.smithy.model.shapes.DocumentShape;
22+
import software.amazon.smithy.model.shapes.MapShape;
23+
import software.amazon.smithy.model.shapes.MemberShape;
24+
import software.amazon.smithy.model.shapes.Shape;
25+
import software.amazon.smithy.model.shapes.ShapeIndex;
26+
import software.amazon.smithy.model.shapes.StructureShape;
27+
import software.amazon.smithy.model.shapes.UnionShape;
28+
import software.amazon.smithy.model.traits.ErrorTrait;
29+
import software.amazon.smithy.model.traits.JsonNameTrait;
30+
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
31+
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
32+
import software.amazon.smithy.typescript.codegen.integration.DocumentMemberDeserVisitor;
33+
import software.amazon.smithy.typescript.codegen.integration.DocumentShapeDeserVisitor;
34+
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
35+
36+
/**
37+
* Visitor to generate deserialization functions for shapes in AWS JSON protocol
38+
* document bodies.
39+
*
40+
* No standard visitation methods are overridden; function body generation for all
41+
* expected deserializers is handled by this class.
42+
*
43+
* Timestamps are deserialized from {@link Format}.EPOCH_SECONDS by default.
44+
*/
45+
final class JsonShapeDeserVisitor extends DocumentShapeDeserVisitor {
46+
47+
JsonShapeDeserVisitor(GenerationContext context) {
48+
super(context);
49+
}
50+
51+
private DocumentMemberDeserVisitor getMemberVisitor(String dataSource) {
52+
return new JsonMemberDeserVisitor(getContext(), dataSource, Format.EPOCH_SECONDS);
53+
}
54+
55+
@Override
56+
protected void deserializeCollection(GenerationContext context, CollectionShape shape) {
57+
TypeScriptWriter writer = context.getWriter();
58+
Shape target = context.getModel().getShapeIndex().getShape(shape.getMember().getTarget()).get();
59+
60+
// Dispatch to the output value provider for any additional handling.
61+
writer.openBlock("return (output || []).map((entry: any) =>", ");", () -> {
62+
writer.write(target.accept(getMemberVisitor("entry")));
63+
});
64+
}
65+
66+
@Override
67+
protected void deserializeDocument(GenerationContext context, DocumentShape shape) {
68+
TypeScriptWriter writer = context.getWriter();
69+
// Documents are JSON content, so don't modify.
70+
writer.write("return output;");
71+
}
72+
73+
@Override
74+
protected void deserializeMap(GenerationContext context, MapShape shape) {
75+
TypeScriptWriter writer = context.getWriter();
76+
Shape target = context.getModel().getShapeIndex().getShape(shape.getValue().getTarget()).get();
77+
78+
// Get the right serialization for each entry in the map. Undefined
79+
// outputs won't have this deserializer invoked.
80+
writer.write("let mapParams: any = {};");
81+
writer.openBlock("Object.keys(output).forEach(key => {", "});", () -> {
82+
// Dispatch to the output value provider for any additional handling.
83+
writer.write("mapParams[key] = $L;", target.accept(getMemberVisitor("output[key]")));
84+
});
85+
writer.write("return mapParams;");
86+
}
87+
88+
@Override
89+
protected void deserializeStructure(GenerationContext context, StructureShape shape) {
90+
TypeScriptWriter writer = context.getWriter();
91+
92+
// Prepare the document contents structure.
93+
// Use a TreeMap to sort the members.
94+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
95+
writer.openBlock("let contents: any = {", "};", () -> {
96+
writer.write("__type: $S,", shape.getId().getName());
97+
if (shape.hasTrait(ErrorTrait.class)) {
98+
writer.write("$$fault: $S,", shape.getTrait(ErrorTrait.class).get().getValue());
99+
}
100+
// Set all the members to undefined to meet type constraints.
101+
members.forEach((memberName, memberShape) -> writer.write("$L: undefined,", memberName));
102+
});
103+
members.forEach((memberName, memberShape) -> {
104+
// Use the jsonName trait value if present, otherwise use the member name.
105+
String locationName = memberShape.getTrait(JsonNameTrait.class)
106+
.map(JsonNameTrait::getValue)
107+
.orElse(memberName);
108+
Shape target = context.getModel().getShapeIndex().getShape(memberShape.getTarget()).get();
109+
110+
// Generate an if statement to set the bodyParam if the member is set.
111+
writer.openBlock("if (output.$L !== undefined) {", "}", locationName, () -> {
112+
writer.write("contents.$L = $L;", memberName,
113+
// Dispatch to the output value provider for any additional handling.
114+
target.accept(getMemberVisitor("output." + locationName)));
115+
});
116+
});
117+
118+
writer.write("return contents;");
119+
}
120+
121+
@Override
122+
protected void deserializeUnion(GenerationContext context, UnionShape shape) {
123+
TypeScriptWriter writer = context.getWriter();
124+
ShapeIndex index = context.getModel().getShapeIndex();
125+
126+
// Check for any known union members and return when we find one.
127+
Map<String, MemberShape> members = new TreeMap<>(shape.getAllMembers());
128+
members.forEach((memberName, memberShape) -> {
129+
Shape target = index.getShape(memberShape.getTarget()).get();
130+
// Use the jsonName trait value if present, otherwise use the member name.
131+
String locationName = memberShape.getTrait(JsonNameTrait.class)
132+
.map(JsonNameTrait::getValue)
133+
.orElse(memberName);
134+
writer.openBlock("if (output.$L !== undefined) {", "}", locationName, () -> {
135+
writer.openBlock("return {", "};", () -> {
136+
// Dispatch to the output value provider for any additional handling.
137+
writer.write("$L: $L", memberName, target.accept(getMemberVisitor("output." + locationName)));
138+
});
139+
});
140+
});
141+
// Or write to the unknown member the element in the output.
142+
writer.write("return { $$unknown: output[Object.keys(output)[0]] };");
143+
}
144+
}

0 commit comments

Comments
 (0)