Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(codegen): allow empty string field values for headers #1412

Merged
merged 4 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/small-gifts-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/smithy-client": patch
---

serialize empty strings and collections in headers
7 changes: 4 additions & 3 deletions packages/smithy-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./NoOpLogger";
export type { DocumentType, SdkError, SmithyException } from "@smithy/types";
export * from "./client";
export * from "./collect-stream-body";
export * from "./command";
Expand All @@ -8,16 +8,17 @@ export * from "./date-utils";
export * from "./default-error-handler";
export * from "./defaults-mode";
export * from "./emitWarningIfUnsupportedVersion";
export * from "./extensions";
export * from "./exceptions";
export * from "./extended-encode-uri-component";
export * from "./extensions";
export * from "./get-array-if-single-item";
export * from "./get-value-from-text-node";
export * from "./is-serializable-header-value";
export * from "./lazy-json";
export * from "./NoOpLogger";
export * from "./object-mapping";
export * from "./parse-utils";
export * from "./resolve-path";
export * from "./ser-utils";
export * from "./serde-json";
export * from "./split-every";
export type { DocumentType, SdkError, SmithyException } from "@smithy/types";
23 changes: 23 additions & 0 deletions packages/smithy-client/src/is-serializable-header-value.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { isSerializableHeaderValue } from "./is-serializable-header-value";

describe(isSerializableHeaderValue.name, () => {
it("considers empty strings serializable", () => {
expect(isSerializableHeaderValue("")).toBe(true);
});

it("considers empty collections serializable", () => {
expect(isSerializableHeaderValue(new Set())).toBe(true);
expect(isSerializableHeaderValue([])).toBe(true);
});

it("considers most falsy data values to be serializable", () => {
expect(isSerializableHeaderValue(false)).toBe(true);
expect(isSerializableHeaderValue(0)).toBe(true);
expect(isSerializableHeaderValue(new Date(0))).toBe(true);
});

it("considered undefined and null to be unserializable", () => {
expect(isSerializableHeaderValue(undefined)).toBe(false);
expect(isSerializableHeaderValue(null)).toBe(false);
});
});
7 changes: 7 additions & 0 deletions packages/smithy-client/src/is-serializable-header-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @internal
* @returns whether the header value is serializable.
*/
export const isSerializableHeaderValue = (value: any) => {
return value != null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ public void generateSharedComponents(GenerationContext context) {
generateDocumentBodyShapeDeserializers(context, deserializingDocumentShapes);
HttpProtocolGeneratorUtils.generateMetadataDeserializer(context, getApplicationProtocol().getResponseType());
HttpProtocolGeneratorUtils.generateCollectBodyString(context);
HttpProtocolGeneratorUtils.generateHttpBindingUtils(context);

writer.write(
context.getStringStore().flushVariableDeclarationCode()
Expand Down Expand Up @@ -965,6 +964,7 @@ private void writeRequestHeaders(
opening = "const headers: any = {";
closing = "};";
} else {
writer.addImport("isSerializableHeaderValue", null, TypeScriptDependency.AWS_SMITHY_CLIENT);
opening = normalHeaderCount > 0
? "const headers: any = map({}, isSerializableHeaderValue, {"
: "const headers: any = map({";
Expand Down Expand Up @@ -1035,6 +1035,8 @@ private void writeNormalHeader(GenerationContext context, HttpBinding binding) {
: headerValue + defaultValue;

// evaluated value has a function or method call attached
context.getWriter()
.addImport("isSerializableHeaderValue", null, TypeScriptDependency.AWS_SMITHY_CLIENT);
headerBuffer.put(headerKey, String.format(
"[%s]: [() => isSerializableHeaderValue(%s), () => %s],",
context.getStringStore().var(headerKey),
Expand Down Expand Up @@ -1093,6 +1095,7 @@ private void writeResponseHeaders(
TypeScriptWriter writer = context.getWriter();

// Headers are always present either from the default document or the payload.
writer.addImport("isSerializableHeaderValue", null, TypeScriptDependency.AWS_SMITHY_CLIENT);
writer.openBlock("let headers: any = map({}, isSerializableHeaderValue, {", "});", () -> {
writeContentTypeHeader(context, operationOrError, false);
injectExtraHeaders.run();
Expand Down Expand Up @@ -1373,7 +1376,13 @@ private String getCollectionInputParam(
dataSource = "Array.from(" + dataSource + ".values())";
}
String collectionTargetValue = getInputValue(context, bindingType, "_entry", targetMember, collectionTarget);
String iteratedParam = "(" + dataSource + " || []).map(_entry => " + collectionTargetValue + " as any)";
String iteratedParam;
if (collectionTargetValue.equals("_entry")) {
iteratedParam = "(" + dataSource + " || [])";
} else {
iteratedParam = "(" + dataSource + " || []).map(_entry => " + collectionTargetValue + " as any)";
}

switch (bindingType) {
case HEADER:
return iteratedParam + ".join(', ')";
Expand Down Expand Up @@ -2689,20 +2698,25 @@ private String getCollectionOutputParam(
switch (bindingType) {
case QUERY_PARAMS:
case QUERY:
if (collectionTargetValue.equals("_entry")) {
return String.format("%1$s", dataSource);
}
return String.format("%1$s.map(_entry => %2$s as any)", dataSource, collectionTargetValue);
case LABEL:
dataSource = "(" + dataSource + " || \"\")";
// Split these values on slashes.
outputParam = "" + dataSource + ".split('/')";

// Iterate over each entry and do deser work.
outputParam += ".map(_entry => " + collectionTargetValue + " as any)";
if (!collectionTargetValue.equals("_entry")) {
outputParam += ".map(_entry => " + collectionTargetValue + " as any)";
}

return outputParam;
case HEADER:
dataSource = "(" + dataSource + " || \"\")";
// Split these values on commas.
outputParam = "" + dataSource + ".split(',')";
outputParam = dataSource + ".split(',')";

// Headers that have HTTP_DATE formatted timestamps already contain a ","
// in their formatted entry, so split on every other "," instead.
Expand All @@ -2719,7 +2733,9 @@ private String getCollectionOutputParam(
}

// Iterate over each entry and do deser work.
outputParam += ".map(_entry => " + collectionTargetValue + " as any)";
if (!collectionTargetValue.equals("_entry")) {
outputParam += ".map(_entry => " + collectionTargetValue + " as any)";
}

return outputParam;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import software.amazon.smithy.typescript.codegen.TypeScriptDependency;
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
import software.amazon.smithy.utils.IoUtils;
import software.amazon.smithy.utils.SmithyInternalApi;
import software.amazon.smithy.utils.SmithyUnstableApi;

Expand Down Expand Up @@ -279,16 +278,6 @@ public static void generateCollectBodyString(GenerationContext context) {
writer.write("");
}

/**
* Writes any additional utils needed for HTTP protocols with bindings.
*
* @param context The generation context.
*/
static void generateHttpBindingUtils(GenerationContext context) {
TypeScriptWriter writer = context.getWriter();
writer.write(IoUtils.readUtf8Resource(HttpProtocolGeneratorUtils.class, "http-binding-utils.ts"));
}

/**
* Writes $retryable key for error if it contains RetryableTrait.
*
Expand Down

This file was deleted.

Loading