Skip to content

Commit

Permalink
handle unset unions
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Sep 27, 2024
1 parent 38d7253 commit d757a15
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-rings-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/smithy-client": minor
---

add quoteHeader function
2 changes: 2 additions & 0 deletions packages/smithy-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export * from "./lazy-json";
export * from "./NoOpLogger";
export * from "./object-mapping";
export * from "./parse-utils";
export * from "./quote-header";
export * from "./resolve-path";
export * from "./ser-utils";
export * from "./serde-json";
export * from "./split-every";
export * from "./split-header";
19 changes: 19 additions & 0 deletions packages/smithy-client/src/quote-header.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { quoteHeader } from "./quote-header";

describe(quoteHeader.name, () => {
it("should not wrap header elements that don't include the delimiter or double quotes", () => {
expect(quoteHeader("bc")).toBe("bc");
});

it("should wrap header elements that include the delimiter", () => {
expect(quoteHeader("b,c")).toBe('"b,c"');
});

it("should wrap header elements that include double quotes", () => {
expect(quoteHeader(`"bc"`)).toBe('"\\"bc\\""');
});

it("should wrap header elements that include the delimiter and double quotes", () => {
expect(quoteHeader(`"b,c"`)).toBe('"\\"b,c\\""');
});
});
11 changes: 11 additions & 0 deletions packages/smithy-client/src/quote-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @public
* @param part - header list element
* @returns quoted string if part contains delimiter.
*/
export function quoteHeader(part: string) {
if (part.includes(",") || part.includes('"')) {
part = `"${part.replace(/"/g, '\\"')}"`;
}
return part;
}
15 changes: 15 additions & 0 deletions packages/smithy-client/src/split-header.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { splitHeader } from "./split-header";

describe(splitHeader.name, () => {
it("should split a string by commas and trim only the comma delimited outer values", () => {
expect(splitHeader("abc")).toEqual(["abc"]);
expect(splitHeader("a,b,c")).toEqual(["a", "b", "c"]);
expect(splitHeader("a, b, c")).toEqual(["a", "b", "c"]);
expect(splitHeader("a , b , c")).toEqual(["a", "b", "c"]);
expect(splitHeader(`a , b , " c "`)).toEqual(["a", "b", " c "]);
});
it("should split a string by commas that are not in quotes, and remove outer quotes", () => {
expect(splitHeader('"b,c", "\\"def\\"", a')).toEqual(["b,c", '"def"', "a"]);
expect(splitHeader('"a,b,c", ""def"", "a,b ,c"')).toEqual(["a,b,c", '"def"', "a,b ,c"]);
});
});
42 changes: 42 additions & 0 deletions packages/smithy-client/src/split-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @param value - header string value.
* @returns value split by commas that aren't in quotes.
*/
export const splitHeader = (value: string): string[] => {
const z = value.length;
const values = [];

let withinQuotes = false;
let prevChar = undefined;
let anchor = 0;

for (let i = 0; i < z; ++i) {
const char = value[i];
switch (char) {
case `"`:
if (prevChar !== "\\") {
withinQuotes = !withinQuotes;
}
break;
case ",":
if (!withinQuotes) {
values.push(value.slice(anchor, i));
anchor = i + 1;
}
break;
default:
}
prevChar = char;
}

values.push(value.slice(anchor));

return values.map((v) => {
v = v.trim();
const z = v.length;
if (v[0] === `"` && v[z - 1] === `"`) {
v = v.slice(1, z - 1);
}
return v.replace(/\\"/g, '"');
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public final class HttpProtocolTestGenerator implements Runnable {
private static final Set<String> IGNORE_COMMA_SPACING = SetUtils.of(
"content-encoding"
);
private static final CharSequence TWO_BACKSLASHES = "\\\\";
private static final CharSequence ONE_BACKSLASH = "\\";

private final TypeScriptSettings settings;
private final Model model;
Expand Down Expand Up @@ -545,9 +547,11 @@ private void writeHttpHostAssertion(HttpRequestTestCase testCase) {
}

private void writeHttpBodyAssertions(String body, String mediaType, boolean isClientTest) {
// If we expect an empty body, expect it to be falsy.
if (body.isEmpty()) {
writer.write("expect(r.body).toBeFalsy();");
// If we expect an empty body, expect it to be falsy.
// Or, for JSON an empty object represents an empty body.
// mediaType is often UNKNOWN here.
writer.write("expect(!r.body || r.body === `{}`).toBeTruthy();");
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,11 @@ private String getCollectionInputParam(

switch (bindingType) {
case HEADER:
if (collectionTarget.isStringShape()) {
context.getWriter().addImport(
"quoteHeader", "__quoteHeader", TypeScriptDependency.AWS_SMITHY_CLIENT);
return iteratedParam + ".map(__quoteHeader).join(', ')";
}
return iteratedParam + ".join(', ')";
case QUERY:
case QUERY_PARAMS:
Expand Down Expand Up @@ -2466,16 +2471,29 @@ private HttpBinding readPayload(
// If payload is a Union, then we need to parse the string into JavaScript object.
importUnionDeserializer(writer);
writer.write("const data: Record<string, any> | undefined "
+ "= __expectUnion(await parseBody(output.body, context));");
+ "= await parseBody(output.body, context);");
} else if (target instanceof StringShape || target instanceof DocumentShape) {
// If payload is String or Document, we need to collect body and convert binary to string.
writer.write("const data: any = await collectBodyString(output.body, context);");
} else {
throw new CodegenException(String.format("Unexpected shape type bound to payload: `%s`",
target.getType()));
}
writer.write("contents.$L = $L;", binding.getMemberName(), getOutputValue(context,

if (target instanceof UnionShape) {
writer.openBlock(
"if (Object.keys(data ?? {}).length) {",
"}",
() -> {
writer.write("contents.$L = __expectUnion($L);", binding.getMemberName(), getOutputValue(context,
Location.PAYLOAD, "data", binding.getMember(), target));
}
);
} else {
writer.write("contents.$L = $L;", binding.getMemberName(), getOutputValue(context,
Location.PAYLOAD, "data", binding.getMember(), target));
}

return binding;
}

Expand Down Expand Up @@ -2716,7 +2734,8 @@ private String getCollectionOutputParam(
case HEADER:
dataSource = "(" + dataSource + " || \"\")";
// Split these values on commas.
outputParam = dataSource + ".split(',')";
context.getWriter().addImport("splitHeader", "__splitHeader", TypeScriptDependency.AWS_SMITHY_CLIENT);
outputParam = "__splitHeader(" + dataSource + ")";

// Headers that have HTTP_DATE formatted timestamps already contain a ","
// in their formatted entry, so split on every other "," instead.
Expand Down

0 comments on commit d757a15

Please sign in to comment.