Skip to content

Commit

Permalink
feat(smithy-client): support strict union parsing (#2746)
Browse files Browse the repository at this point in the history
Unions must be JSON objects that only have one key set.
  • Loading branch information
adamthom-amzn committed Sep 9, 2021
1 parent eedf577 commit 1159680
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/smithy-client/src/parse-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
expectNonNull,
expectObject,
expectShort,
expectUnion,
limitedParseDouble,
limitedParseFloat32,
parseBoolean,
Expand Down Expand Up @@ -313,6 +314,27 @@ describe("expectString", () => {
});
});

describe("expectUnion", () => {
it.each([null, undefined])("accepts %s", (value) => {
expect(expectUnion(value)).toEqual(undefined);
});
describe("rejects non-objects", () => {
it.each([1, NaN, Infinity, -Infinity, true, false, [], "abc"])("%s", (value) => {
expect(() => expectUnion(value)).toThrowError();
});
});
describe("rejects malformed unions", () => {
it.each([{}, { a: null }, { a: undefined }, { a: 1, b: 2 }])("%s", (value) => {
expect(() => expectUnion(value)).toThrowError();
});
});
describe("accepts unions", () => {
it.each([{ a: 1 }, { a: 1, b: null }])("%s", (value) => {
expect(expectUnion(value)).toEqual(value);
});
});
});

describe("strictParseDouble", () => {
describe("accepts non-numeric floats as strings", () => {
expect(strictParseDouble("Infinity")).toEqual(Infinity);
Expand Down
30 changes: 30 additions & 0 deletions packages/smithy-client/src/parse-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,36 @@ export const expectString = (value: any): string | undefined => {
throw new TypeError(`Expected string, got ${typeof value}`);
};

/**
* Asserts a value is a JSON-like object with only one non-null/non-undefined key and
* returns it.
*
* @param value A value that is expected to be an object with exactly one non-null,
* non-undefined key.
* @return the value if it's a union, undefined if it's null/undefined, otherwise
* an error is thrown.
*/
export const expectUnion = (value: unknown): { [key: string]: any } | undefined => {
if (value === null || value === undefined) {
return undefined;
}
const asObject = expectObject(value)!;

const setKeys = Object.entries(asObject)
.filter(([_, v]) => v !== null && v !== undefined)
.map(([k, _]) => k);

if (setKeys.length === 0) {
throw new TypeError(`Unions must have exactly one non-null member`);
}

if (setKeys.length > 1) {
throw new TypeError(`Unions must have exactly one non-null member. Keys ${setKeys} were not null.`);
}

return asObject;
};

/**
* Parses a value into a double. If the value is null or undefined, undefined
* will be returned. If the value is a string, it will be parsed by the standard
Expand Down

0 comments on commit 1159680

Please sign in to comment.