Skip to content

Commit 1159680

Browse files
feat(smithy-client): support strict union parsing (#2746)
Unions must be JSON objects that only have one key set.
1 parent eedf577 commit 1159680

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

Diff for: packages/smithy-client/src/parse-utils.spec.ts

+22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
expectNonNull,
77
expectObject,
88
expectShort,
9+
expectUnion,
910
limitedParseDouble,
1011
limitedParseFloat32,
1112
parseBoolean,
@@ -313,6 +314,27 @@ describe("expectString", () => {
313314
});
314315
});
315316

317+
describe("expectUnion", () => {
318+
it.each([null, undefined])("accepts %s", (value) => {
319+
expect(expectUnion(value)).toEqual(undefined);
320+
});
321+
describe("rejects non-objects", () => {
322+
it.each([1, NaN, Infinity, -Infinity, true, false, [], "abc"])("%s", (value) => {
323+
expect(() => expectUnion(value)).toThrowError();
324+
});
325+
});
326+
describe("rejects malformed unions", () => {
327+
it.each([{}, { a: null }, { a: undefined }, { a: 1, b: 2 }])("%s", (value) => {
328+
expect(() => expectUnion(value)).toThrowError();
329+
});
330+
});
331+
describe("accepts unions", () => {
332+
it.each([{ a: 1 }, { a: 1, b: null }])("%s", (value) => {
333+
expect(expectUnion(value)).toEqual(value);
334+
});
335+
});
336+
});
337+
316338
describe("strictParseDouble", () => {
317339
describe("accepts non-numeric floats as strings", () => {
318340
expect(strictParseDouble("Infinity")).toEqual(Infinity);

Diff for: packages/smithy-client/src/parse-utils.ts

+30
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,36 @@ export const expectString = (value: any): string | undefined => {
222222
throw new TypeError(`Expected string, got ${typeof value}`);
223223
};
224224

225+
/**
226+
* Asserts a value is a JSON-like object with only one non-null/non-undefined key and
227+
* returns it.
228+
*
229+
* @param value A value that is expected to be an object with exactly one non-null,
230+
* non-undefined key.
231+
* @return the value if it's a union, undefined if it's null/undefined, otherwise
232+
* an error is thrown.
233+
*/
234+
export const expectUnion = (value: unknown): { [key: string]: any } | undefined => {
235+
if (value === null || value === undefined) {
236+
return undefined;
237+
}
238+
const asObject = expectObject(value)!;
239+
240+
const setKeys = Object.entries(asObject)
241+
.filter(([_, v]) => v !== null && v !== undefined)
242+
.map(([k, _]) => k);
243+
244+
if (setKeys.length === 0) {
245+
throw new TypeError(`Unions must have exactly one non-null member`);
246+
}
247+
248+
if (setKeys.length > 1) {
249+
throw new TypeError(`Unions must have exactly one non-null member. Keys ${setKeys} were not null.`);
250+
}
251+
252+
return asObject;
253+
};
254+
225255
/**
226256
* Parses a value into a double. If the value is null or undefined, undefined
227257
* will be returned. If the value is a string, it will be parsed by the standard

0 commit comments

Comments
 (0)