Skip to content

Commit f16ee81

Browse files
committed
Fix several serde issues with HTTP headers
This commit fixes casing comparison issues related to fetch for HTTP bindings of headers and prefix headers for both request and response. It also includes a fix to more robustly validate headers before serialization.
1 parent b9300aa commit f16ee81

File tree

3 files changed

+31
-11
lines changed

3 files changed

+31
-11
lines changed

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

+14-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.Comparator;
2121
import java.util.List;
22+
import java.util.Locale;
2223
import java.util.Map;
2324
import java.util.Optional;
2425
import java.util.Set;
@@ -146,6 +147,7 @@ public void generateSharedComponents(GenerationContext context) {
146147
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
147148
HttpProtocolGeneratorUtils.generateCollectBody(context);
148149
HttpProtocolGeneratorUtils.generateCollectBodyString(context);
150+
HttpProtocolGeneratorUtils.generateHttpBindingUtils(context);
149151
}
150152

151153
/**
@@ -355,26 +357,26 @@ private void writeHeaders(
355357
operation.getInput().ifPresent(outputId -> {
356358
Model model = context.getModel();
357359
for (HttpBinding binding : bindingIndex.getRequestBindings(operation, Location.HEADER)) {
358-
String memberName = symbolProvider.toMemberName(binding.getMember());
359-
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> {
360+
String memberLocation = "input." + symbolProvider.toMemberName(binding.getMember());
361+
writer.openBlock("if (isSerializableHeaderValue($1L)) {", "}", memberLocation, () -> {
360362
Shape target = model.expectShape(binding.getMember().getTarget());
361-
String headerValue = getInputValue(context, binding.getLocation(), "input." + memberName,
363+
String headerValue = getInputValue(context, binding.getLocation(), memberLocation + "!",
362364
binding.getMember(), target);
363365
writer.write("headers[$S] = $L;", binding.getLocationName(), headerValue);
364366
});
365367
}
366368

367369
// Handle assembling prefix headers.
368370
for (HttpBinding binding : bindingIndex.getRequestBindings(operation, Location.PREFIX_HEADERS)) {
369-
String memberName = symbolProvider.toMemberName(binding.getMember());
370-
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> {
371+
String memberLocation = "input." + symbolProvider.toMemberName(binding.getMember());
372+
writer.openBlock("if ($L !== undefined) {", "}", memberLocation, () -> {
371373
MapShape prefixMap = model.expectShape(binding.getMember().getTarget()).asMapShape().get();
372374
Shape target = model.expectShape(prefixMap.getValue().getTarget());
373375
// Iterate through each entry in the member.
374-
writer.openBlock("Object.keys(input.$L).forEach(suffix => {", "});", memberName, () -> {
376+
writer.openBlock("Object.keys($L).forEach(suffix => {", "});", memberLocation, () -> {
375377
// Use a ! since we already validated the input member is defined above.
376378
String headerValue = getInputValue(context, binding.getLocation(),
377-
"input." + memberName + "![suffix]", binding.getMember(), target);
379+
memberLocation + "![suffix]", binding.getMember(), target);
378380
// Append the suffix to the defined prefix and serialize the value in to that key.
379381
writer.write("headers[$S + suffix] = $L;", binding.getLocationName(), headerValue);
380382
});
@@ -951,7 +953,7 @@ private void readHeaders(
951953
Model model = context.getModel();
952954
for (HttpBinding binding : bindingIndex.getResponseBindings(operationOrError, Location.HEADER)) {
953955
String memberName = symbolProvider.toMemberName(binding.getMember());
954-
String headerName = binding.getLocationName().toLowerCase();
956+
String headerName = binding.getLocationName().toLowerCase(Locale.US);
955957
writer.openBlock("if ($L.headers[$S] !== undefined) {", "}", outputName, headerName, () -> {
956958
Shape target = model.expectShape(binding.getMember().getTarget());
957959
String headerValue = getOutputValue(context, binding.getLocation(),
@@ -973,16 +975,17 @@ private void readHeaders(
973975
writer.write("contents.$L = {};", memberName);
974976
});
975977

976-
// Generate a single block for each group of prefix headers.
977-
writer.openBlock("if (header.startsWith($S)) {", "}", binding.getLocationName(), () -> {
978+
// Generate a single block for each group of lower-cased prefix headers.
979+
String headerLocation = binding.getLocationName().toLowerCase(Locale.US);
980+
writer.openBlock("if (header.startsWith($S)) {", "}", headerLocation, () -> {
978981
MapShape prefixMap = model.expectShape(binding.getMember().getTarget()).asMapShape().get();
979982
Shape target = model.expectShape(prefixMap.getValue().getTarget());
980983
String headerValue = getOutputValue(context, binding.getLocation(),
981984
outputName + ".headers[header]", binding.getMember(), target);
982985

983986
// Extract the non-prefix portion as the key.
984987
writer.write("contents.$L[header.substring($L)] = $L;",
985-
memberName, binding.getLocationName().length(), headerValue);
988+
memberName, headerLocation.length(), headerValue);
986989
});
987990
}
988991
});

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

+11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
4141
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
4242
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
43+
import software.amazon.smithy.utils.IoUtils;
4344

4445
/**
4546
* Utility methods for generating HTTP protocols.
@@ -233,6 +234,16 @@ static void generateCollectBodyString(GenerationContext context) {
233234
writer.write("");
234235
}
235236

237+
/**
238+
* Writes any additional utils needed for HTTP protocols with bindings.
239+
*
240+
* @param context The generation context.
241+
*/
242+
static void generateHttpBindingUtils(GenerationContext context) {
243+
TypeScriptWriter writer = context.getWriter();
244+
writer.write(IoUtils.readUtf8Resource(HttpProtocolGeneratorUtils.class, "http-binding-utils.ts"));
245+
}
246+
236247
/**
237248
* Writes a function used to dispatch to the proper error deserializer
238249
* for each error that the operation can return. The generated function
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function isSerializableHeaderValue(value: any): boolean {
2+
return value !== undefined
3+
&& value !== ""
4+
&& (!Object.getOwnPropertyNames(value).includes("length") || value.length != 0)
5+
&& (!Object.getOwnPropertyNames(value).includes("size") || value.size != 0);
6+
}

0 commit comments

Comments
 (0)