Skip to content

Commit 038bc6e

Browse files
committed
Updates for HTTP serde correctness and safety
This commit includes updates to Http ProtocolGenerator components for correctness and safety. 1. It fixes generation issues for the @httpPrefixHeaders trait in the HttpBindingProtocolGenerator. 2. It updates SerdeContext to use an import alias for safety. 3. It only generates a local parsedOutput if it will be consumed. 4. It adds the Error type to the list of Typescript reserved words.
1 parent bdfed1c commit 038bc6e

File tree

6 files changed

+37
-29
lines changed

6 files changed

+37
-29
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ protected final void generateDeserFunction(
305305
writer.addImport(symbol, symbol.getName());
306306
writer.openBlock("const $L = (\n"
307307
+ " output: any,\n"
308-
+ " context: SerdeContext\n"
308+
+ " context: __SerdeContext\n"
309309
+ "): $T => {", "}", methodName, symbol, () -> functionBody.accept(context, shape));
310310
writer.write("");
311311
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ private void generateSerFunction(
300300
writer.addImport(symbol, symbol.getName());
301301
writer.openBlock("const $L = (\n"
302302
+ " input: $T,\n"
303-
+ " context: SerdeContext\n"
303+
+ " context: __SerdeContext\n"
304304
+ "): any => {", "}", methodName, symbol, () -> functionBody.accept(context, shape));
305305
writer.write("");
306306
}

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import software.amazon.smithy.model.shapes.DocumentShape;
3737
import software.amazon.smithy.model.shapes.DoubleShape;
3838
import software.amazon.smithy.model.shapes.FloatShape;
39+
import software.amazon.smithy.model.shapes.MapShape;
3940
import software.amazon.smithy.model.shapes.MemberShape;
4041
import software.amazon.smithy.model.shapes.NumberShape;
4142
import software.amazon.smithy.model.shapes.OperationShape;
@@ -169,7 +170,7 @@ private void generateOperationSerializer(
169170

170171
// Ensure that the request type is imported.
171172
writer.addUseImports(requestType);
172-
writer.addImport("SerdeContext", "SerdeContext", "@aws-sdk/types");
173+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
173174
writer.addImport("Endpoint", "__Endpoint", "@aws-sdk/types");
174175
// e.g., serializeAws_restJson1_1ExecuteStatement
175176
String methodName = ProtocolGenerator.getSerFunctionName(symbol, getName());
@@ -178,7 +179,7 @@ private void generateOperationSerializer(
178179

179180
writer.openBlock("export async function $L(\n"
180181
+ " input: $T,\n"
181-
+ " context: SerdeContext\n"
182+
+ " context: __SerdeContext\n"
182183
+ "): Promise<$T> {", "}", methodName, inputType, requestType, () -> {
183184
List<HttpBinding> labelBindings = writeRequestLabels(context, operation, bindingIndex, trait);
184185
List<HttpBinding> queryBindings = writeRequestQueryString(context, operation, bindingIndex);
@@ -303,11 +304,13 @@ private void writeHeaders(
303304
for (HttpBinding binding : bindingIndex.getRequestBindings(operation, Location.PREFIX_HEADERS)) {
304305
String memberName = symbolProvider.toMemberName(binding.getMember());
305306
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> {
306-
Shape target = index.getShape(binding.getMember().getTarget()).get();
307+
MapShape prefixMap = index.getShape(binding.getMember().getTarget()).get().asMapShape().get();
308+
Shape target = index.getShape(prefixMap.getValue().getTarget()).get();
307309
// Iterate through each entry in the member.
308-
writer.openBlock("Object.keys(input.$L).forEach(suffix -> {", "});", memberName, () -> {
310+
writer.openBlock("Object.keys(input.$L).forEach(suffix => {", "});", memberName, () -> {
311+
// Use a ! since we already validated the input member is defined above.
309312
String headerValue = getInputValue(context, binding.getLocation(),
310-
"input." + memberName + "[suffix]", binding.getMember(), target);
313+
"input." + memberName + "![suffix]", binding.getMember(), target);
311314
// Append the suffix to the defined prefix and serialize the value in to that key.
312315
writer.write("headers[$S + suffix] = $L;", binding.getLocationName(), headerValue);
313316
});
@@ -529,7 +532,7 @@ private void generateOperationDeserializer(
529532

530533
// Ensure that the response type is imported.
531534
writer.addUseImports(responseType);
532-
writer.addImport("SerdeContext", "SerdeContext", "@aws-sdk/types");
535+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
533536
// e.g., deserializeAws_restJson1_1ExecuteStatement
534537
String methodName = ProtocolGenerator.getDeserFunctionName(symbol, getName());
535538
String errorMethodName = methodName + "Error";
@@ -539,7 +542,7 @@ private void generateOperationDeserializer(
539542
// Handle the general response.
540543
writer.openBlock("export async function $L(\n"
541544
+ " output: $T,\n"
542-
+ " context: SerdeContext\n"
545+
+ " context: __SerdeContext\n"
543546
+ "): Promise<$T> {", "}", methodName, responseType, outputType, () -> {
544547
// Redirect error deserialization to the dispatcher
545548
writer.openBlock("if (output.statusCode !== $L) {", "}", trait.getCode(), () -> {
@@ -588,7 +591,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
588591

589592
writer.openBlock("const $L = (\n"
590593
+ " output: any,\n"
591-
+ " context: SerdeContext\n"
594+
+ " context: __SerdeContext\n"
592595
+ "): $T => {", "};", errorDeserMethodName, errorSymbol, () -> {
593596
writer.write("const data: any = output.body;");
594597

@@ -638,23 +641,24 @@ private void readHeaders(
638641
bindingIndex.getResponseBindings(operationOrError, Location.PREFIX_HEADERS);
639642
if (!prefixHeaderBindings.isEmpty()) {
640643
// Run through the headers one time, matching any prefix groups.
641-
writer.openBlock("Object.keys(output.headers).forEach(header -> {", "});", () -> {
644+
writer.openBlock("Object.keys(output.headers).forEach(header => {", "});", () -> {
642645
for (HttpBinding binding : prefixHeaderBindings) {
643646
// Generate a single block for each group of prefix headers.
644-
writer.openBlock("if (header.startsWith($L)) {", binding.getLocationName(), "}", () -> {
647+
writer.openBlock("if (header.startsWith($S)) {", "}", binding.getLocationName(), () -> {
645648
String memberName = symbolProvider.toMemberName(binding.getMember());
646-
Shape target = context.getModel().getShapeIndex()
647-
.getShape(binding.getMember().getTarget()).get();
649+
MapShape prefixMap = index.getShape(binding.getMember().getTarget()).get().asMapShape().get();
650+
Shape target = index.getShape(prefixMap.getValue().getTarget()).get();
648651
String headerValue = getOutputValue(context, binding.getLocation(),
649652
"output.headers[header]", binding.getMember(), target);
650653

651654
// Prepare a grab bag for these headers if necessary
652655
writer.openBlock("if (contents.$L === undefined) {", "}", memberName, () -> {
653-
writer.write("contents.$L: any = {};", memberName);
656+
writer.write("contents.$L = {};", memberName);
654657
});
655658

656659
// Extract the non-prefix portion as the key.
657-
writer.write("contents.$L[header.substring(header.length)] = $L;", memberName, headerValue);
660+
writer.write("contents.$L[header.substring($L)] = $L;",
661+
memberName, binding.getLocationName().length(), headerValue);
658662
});
659663
}
660664
});

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,17 @@ static Set<StructureShape> generateErrorDispatcher(
135135

136136
writer.openBlock("async function $L(\n"
137137
+ " output: $T,\n"
138-
+ " context: SerdeContext,\n"
138+
+ " context: __SerdeContext,\n"
139139
+ "): Promise<$T> {", "}", errorMethodName, responseType, outputType, () -> {
140140
writer.write("const data: any = await parseBody(output.body, context);");
141-
// Create a holding object since we have already parsed the body, but retain the rest of the output.
142-
writer.openBlock("const parsedOutput: any = {", "};", () -> {
143-
writer.write("...output,");
144-
writer.write("body: data,");
145-
});
141+
// We only consume the parsedOutput if we're dispatching, so only generate if we will.
142+
if (!operation.getErrors().isEmpty()) {
143+
// Create a holding object since we have already parsed the body, but retain the rest of the output.
144+
writer.openBlock("const parsedOutput: any = {", "};", () -> {
145+
writer.write("...output,");
146+
writer.write("body: data,");
147+
});
148+
}
146149
writer.write("let response: any;");
147150
writer.write("let errorCode: String;");
148151
errorCodeGenerator.accept(context);

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,17 @@ private void generateOperationSerializer(GenerationContext context, OperationSha
110110

111111
// Ensure that the request type is imported.
112112
writer.addUseImports(requestType);
113-
writer.addImport("SerdeContext", "SerdeContext", "@aws-sdk/types");
113+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
114114
writer.addImport("Endpoint", "__Endpoint", "@aws-sdk/types");
115115
// e.g., serializeAws_restJson1_1ExecuteStatement
116116
String methodName = ProtocolGenerator.getSerFunctionName(symbol, getName());
117117
// Add the normalized input type.
118118
Symbol inputType = symbol.expectProperty("inputType", Symbol.class);
119119

120120
writer.openBlock("export async function $L(\n"
121-
+ " input: $T,\n"
122-
+ " context: SerdeContext\n"
123-
+ "): Promise<$T> {", "}", methodName, inputType, requestType, () -> {
121+
+ " input: $T,\n"
122+
+ " context: __SerdeContext\n"
123+
+ "): Promise<$T> {", "}", methodName, inputType, requestType, () -> {
124124
writeRequestHeaders(context, operation);
125125
boolean hasRequestBody = writeRequestBody(context, operation);
126126

@@ -220,7 +220,7 @@ private void generateOperationDeserializer(GenerationContext context, OperationS
220220

221221
// Ensure that the response type is imported.
222222
writer.addUseImports(responseType);
223-
writer.addImport("SerdeContext", "SerdeContext", "@aws-sdk/types");
223+
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types");
224224
// e.g., deserializeAws_restJson1_1ExecuteStatement
225225
String methodName = ProtocolGenerator.getDeserFunctionName(symbol, getName());
226226
String errorMethodName = methodName + "Error";
@@ -230,7 +230,7 @@ private void generateOperationDeserializer(GenerationContext context, OperationS
230230
// Handle the general response.
231231
writer.openBlock("export async function $L(\n"
232232
+ " output: $T,\n"
233-
+ " context: SerdeContext\n"
233+
+ " context: __SerdeContext\n"
234234
+ "): Promise<$T> {", "}", methodName, responseType, outputType, () -> {
235235
// Redirect error deserialization to the dispatcher
236236
writer.openBlock("if (output.statusCode >= 400) {", "}", () -> {
@@ -272,7 +272,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
272272

273273
writer.openBlock("const $L = (\n"
274274
+ " output: any,\n"
275-
+ " context: SerdeContext\n"
275+
+ " context: __SerdeContext\n"
276276
+ "): $T => {", "};", errorDeserMethodName, errorSymbol, () -> {
277277
// First deserialize the body properly.
278278
writer.write("const deserialized: any = $L(output.body, context);",

smithy-typescript-codegen/src/main/resources/software/amazon/smithy/typescript/codegen/reserved-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Blob
9292
Boolean
9393
ConstructorParameters
9494
Date
95+
Error
9596
Exclude
9697
Extract
9798
Infinity

0 commit comments

Comments
 (0)