Skip to content

Commit

Permalink
feat(smithy-client): support strict union parsing
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 2, 2021
1 parent afeccd7 commit 93f6c03
Show file tree
Hide file tree
Showing 2 changed files with 55 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
33 changes: 33 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,39 @@ 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 = new Set<string>();
for (const [key, value] of Object.entries(asObject)) {
if (value !== null && value !== undefined) {
setKeys.add(key);
}
}

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

if (setKeys.size > 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 93f6c03

Please sign in to comment.