Skip to content

Commit b879c18

Browse files
committed
fix(util-dynamodb): allow marshall function to handle more input types
1 parent 683beb4 commit b879c18

File tree

4 files changed

+190
-5
lines changed

4 files changed

+190
-5
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { marshallInput } from "./utils";
2+
3+
describe("utils integration", () => {
4+
describe("falsy values", () => {
5+
it("marshalInput should not ignore falsy values", () => {
6+
expect(marshallInput({ Items: [0, false, null, ""] }, [{ key: "Items" }])).toEqual({
7+
Items: [{ N: "0" }, { BOOL: false }, { NULL: true }, { S: "" }],
8+
});
9+
});
10+
});
11+
});

lib/lib-dynamodb/src/commands/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type AllNodes = {
1010
};
1111

1212
const processObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => {
13-
if (obj) {
13+
if (obj !== undefined) {
1414
if (!children || (Array.isArray(children) && children.length === 0)) {
1515
// Leaf of KeyNode, process the object.
1616
return processFunc(obj);
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { marshallInput } from "../../../lib/lib-dynamodb/src/commands/utils";
2+
import { marshall } from "./marshall";
3+
import { convertToAttr } from "./convertToAttr";
4+
5+
describe("marshall integration", () => {
6+
describe("behaves as convertToAttr for non-collection values or Sets", () => {
7+
it("marshals string", () => {
8+
const value = "hello";
9+
expect(marshall(value)).toEqual(convertToAttr(value));
10+
});
11+
it("marshals number", () => {
12+
const value = 1578;
13+
expect(marshall(value)).toEqual(convertToAttr(value));
14+
});
15+
it("marshals binary", () => {
16+
const value = new Uint8Array([0, 1, 0, 1]);
17+
expect(marshall(value)).toEqual(convertToAttr(value));
18+
});
19+
it("marshals boolean", () => {
20+
let value = false;
21+
expect(marshall(value)).toEqual(convertToAttr(value));
22+
value = true;
23+
expect(marshall(value)).toEqual(convertToAttr(value));
24+
});
25+
it("marshals null", () => {
26+
const value = null;
27+
expect(marshall(value)).toEqual(convertToAttr(value));
28+
});
29+
it("marshals string set", () => {
30+
const value = new Set(["a", "b"]);
31+
expect(marshall(value)).toEqual(convertToAttr(value));
32+
});
33+
it("marshals number set", () => {
34+
const value = new Set([1, 2]);
35+
expect(marshall(value)).toEqual(convertToAttr(value));
36+
});
37+
it("marshals binary set", () => {
38+
const value = new Set([new Uint8Array([1, 0]), new Uint8Array([0, 1])]);
39+
expect(marshall(value)).toEqual(convertToAttr(value));
40+
});
41+
});
42+
describe("unwraps one level for input data which are lists or maps", () => {
43+
it("marshals and unwraps map", () => {
44+
expect(marshall({ a: 1, b: { a: 2, b: [1, 2, 3] } })).toEqual({
45+
a: { N: "1" },
46+
b: {
47+
M: {
48+
a: { N: "2" },
49+
b: {
50+
L: [{ N: "1" }, { N: "2" }, { N: "3" }],
51+
},
52+
},
53+
},
54+
});
55+
});
56+
it("marshals and unwraps list", () => {
57+
expect(marshall(["test", 2, null])).toEqual([{ S: "test" }, { N: "2" }, { NULL: true }]);
58+
});
59+
});
60+
});
61+
62+
describe("marshallInput command integration", () => {
63+
it("marshals QueryCommand input", () => {
64+
const input = {
65+
TableName: "TestTable",
66+
KeyConditions: {
67+
id: {
68+
AttributeValueList: ["test"],
69+
ComparisonOperator: "EQ",
70+
},
71+
},
72+
};
73+
const inputKeyNodes = [
74+
{
75+
key: "KeyConditions",
76+
children: {
77+
children: [{ key: "AttributeValueList" }],
78+
},
79+
},
80+
{
81+
key: "QueryFilter",
82+
children: {
83+
children: [{ key: "AttributeValueList" }],
84+
},
85+
},
86+
{ key: "ExclusiveStartKey" },
87+
{ key: "ExpressionAttributeValues" },
88+
];
89+
const output = {
90+
TableName: "TestTable",
91+
KeyConditions: { id: { AttributeValueList: [{ S: "test" }], ComparisonOperator: "EQ" } },
92+
QueryFilter: undefined,
93+
ExclusiveStartKey: undefined,
94+
ExpressionAttributeValues: undefined,
95+
};
96+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
97+
});
98+
it("marshals ExecuteStatementCommand input", () => {
99+
const input = {
100+
Statement: `SELECT col_1
101+
FROM some_table
102+
WHERE contains("col_1", ?)`,
103+
Parameters: ["some_param"],
104+
};
105+
const inputKeyNodes = [{ key: "Parameters" }];
106+
const output = {
107+
Statement: input.Statement,
108+
Parameters: [{ S: "some_param" }],
109+
};
110+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
111+
});
112+
it("marshals BatchExecuteStatementCommand input", () => {
113+
const input = {
114+
Statements: [
115+
{
116+
Statement: `
117+
UPDATE "table"
118+
SET field1=?
119+
WHERE field2=?
120+
AND field3=?
121+
`,
122+
Parameters: [false, "field 2 value", 1234],
123+
},
124+
],
125+
};
126+
const inputKeyNodes = [{ key: "Statements", children: [{ key: "Parameters" }] }];
127+
const output = {
128+
Statements: [
129+
{
130+
Statement: input.Statements[0].Statement,
131+
Parameters: [
132+
{
133+
BOOL: false,
134+
},
135+
{ S: "field 2 value" },
136+
{ N: "1234" },
137+
],
138+
},
139+
],
140+
};
141+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
142+
});
143+
});

packages/util-dynamodb/src/marshall.ts

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AttributeValue } from "@aws-sdk/client-dynamodb";
22

33
import { convertToAttr } from "./convertToAttr";
4-
import { NativeAttributeValue } from "./models";
4+
import { NativeAttributeBinary, NativeAttributeValue } from "./models";
55

66
/**
77
* An optional configuration object for `marshall`
@@ -26,8 +26,39 @@ export interface marshallOptions {
2626
*
2727
* @param {any} data - The data to convert to a DynamoDB record
2828
* @param {marshallOptions} options - An optional configuration object for `marshall`
29+
*
2930
*/
30-
export const marshall = <T extends { [K in keyof T]: NativeAttributeValue }>(
31-
data: T,
31+
export function marshall(data: Set<string>, options?: marshallOptions): AttributeValue.SSMember;
32+
export function marshall(data: Set<number>, options?: marshallOptions): AttributeValue.NSMember;
33+
export function marshall(data: Set<NativeAttributeBinary>, options?: marshallOptions): AttributeValue.BSMember;
34+
export function marshall<M extends { [K in keyof M]: NativeAttributeValue }>(
35+
data: M,
3236
options?: marshallOptions
33-
): { [key: string]: AttributeValue } => convertToAttr(data, options).M as { [key: string]: AttributeValue };
37+
): Record<string, AttributeValue>;
38+
export function marshall<L extends NativeAttributeValue[]>(data: L, options?: marshallOptions): AttributeValue[];
39+
export function marshall(data: string, options?: marshallOptions): AttributeValue.SMember;
40+
export function marshall(data: number, options?: marshallOptions): AttributeValue.NMember;
41+
export function marshall(data: NativeAttributeBinary, options?: marshallOptions): AttributeValue.BMember;
42+
export function marshall(data: null, options?: marshallOptions): AttributeValue.NULLMember;
43+
export function marshall(data: boolean, options?: marshallOptions): AttributeValue.BOOLMember;
44+
export function marshall(data: unknown, options?: marshallOptions): AttributeValue.$UnknownMember;
45+
export function marshall(data: unknown, options?: marshallOptions) {
46+
const attributeValue: AttributeValue = convertToAttr(data, options);
47+
const [key, value] = Object.entries(attributeValue)[0];
48+
switch (key) {
49+
case "M":
50+
case "L":
51+
return value;
52+
case "SS":
53+
case "NS":
54+
case "BS":
55+
case "S":
56+
case "N":
57+
case "B":
58+
case "NULL":
59+
case "BOOL":
60+
case "$unknown":
61+
default:
62+
return attributeValue;
63+
}
64+
}

0 commit comments

Comments
 (0)