Skip to content

Commit a5fa267

Browse files
authored
fix(util-dynamodb): allow marshall function to handle more input types (#3539)
* fix(util-dynamodb): allow marshall function to handle more input types * fix(util-dynamodb): revert marshall.spec.ts and minor formatting * fix(util-dynamodb): code spacing
1 parent 7480667 commit a5fa267

File tree

4 files changed

+195
-5
lines changed

4 files changed

+195
-5
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { marshallInput } from "./utils";
2+
3+
describe("marshallInput and processObj", () => {
4+
it("marshallInput should not ignore falsy values", () => {
5+
expect(marshallInput({ Items: [0, false, null, ""] }, [{ key: "Items" }])).toEqual({
6+
Items: [{ N: "0" }, { BOOL: false }, { NULL: true }, { S: "" }],
7+
});
8+
});
9+
});
10+
11+
describe("marshallInput for commands", () => {
12+
it("marshals QueryCommand input", () => {
13+
const input = {
14+
TableName: "TestTable",
15+
KeyConditions: {
16+
id: {
17+
AttributeValueList: ["test"],
18+
ComparisonOperator: "EQ",
19+
},
20+
},
21+
};
22+
const inputKeyNodes = [
23+
{
24+
key: "KeyConditions",
25+
children: {
26+
children: [{ key: "AttributeValueList" }],
27+
},
28+
},
29+
{
30+
key: "QueryFilter",
31+
children: {
32+
children: [{ key: "AttributeValueList" }],
33+
},
34+
},
35+
{ key: "ExclusiveStartKey" },
36+
{ key: "ExpressionAttributeValues" },
37+
];
38+
const output = {
39+
TableName: "TestTable",
40+
KeyConditions: { id: { AttributeValueList: [{ S: "test" }], ComparisonOperator: "EQ" } },
41+
QueryFilter: undefined,
42+
ExclusiveStartKey: undefined,
43+
ExpressionAttributeValues: undefined,
44+
};
45+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
46+
});
47+
it("marshals ExecuteStatementCommand input", () => {
48+
const input = {
49+
Statement: `SELECT col_1
50+
FROM some_table
51+
WHERE contains("col_1", ?)`,
52+
Parameters: ["some_param"],
53+
};
54+
const inputKeyNodes = [{ key: "Parameters" }];
55+
const output = {
56+
Statement: input.Statement,
57+
Parameters: [{ S: "some_param" }],
58+
};
59+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
60+
});
61+
it("marshals BatchExecuteStatementCommand input", () => {
62+
const input = {
63+
Statements: [
64+
{
65+
Statement: `
66+
UPDATE "table"
67+
SET field1=?
68+
WHERE field2 = ?
69+
AND field3 = ?
70+
`,
71+
Parameters: [false, "field 2 value", 1234],
72+
},
73+
],
74+
};
75+
const inputKeyNodes = [{ key: "Statements", children: [{ key: "Parameters" }] }];
76+
const output = {
77+
Statements: [
78+
{
79+
Statement: input.Statements[0].Statement,
80+
Parameters: [
81+
{
82+
BOOL: false,
83+
},
84+
{ S: "field 2 value" },
85+
{ N: "1234" },
86+
],
87+
},
88+
],
89+
};
90+
expect(marshallInput(input, inputKeyNodes)).toEqual(output);
91+
});
92+
});

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);

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+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { convertToAttr } from "./convertToAttr";
2+
import { marshall } from "./marshall";
3+
4+
describe("marshall type discernment", () => {
5+
describe("behaves as convertToAttr for non-collection values or Sets", () => {
6+
it("marshals string", () => {
7+
const value = "hello";
8+
expect(marshall(value)).toEqual(convertToAttr(value));
9+
});
10+
11+
it("marshals number", () => {
12+
const value = 1578;
13+
expect(marshall(value)).toEqual(convertToAttr(value));
14+
});
15+
16+
it("marshals binary", () => {
17+
const value = new Uint8Array([0, 1, 0, 1]);
18+
expect(marshall(value)).toEqual(convertToAttr(value));
19+
});
20+
21+
it("marshals boolean", () => {
22+
let value = false;
23+
expect(marshall(value)).toEqual(convertToAttr(value));
24+
value = true;
25+
expect(marshall(value)).toEqual(convertToAttr(value));
26+
});
27+
28+
it("marshals null", () => {
29+
const value = null;
30+
expect(marshall(value)).toEqual(convertToAttr(value));
31+
});
32+
it("marshals string set", () => {
33+
const value = new Set(["a", "b"]);
34+
expect(marshall(value)).toEqual(convertToAttr(value));
35+
});
36+
37+
it("marshals number set", () => {
38+
const value = new Set([1, 2]);
39+
expect(marshall(value)).toEqual(convertToAttr(value));
40+
});
41+
42+
it("marshals binary set", () => {
43+
const value = new Set([new Uint8Array([1, 0]), new Uint8Array([0, 1])]);
44+
expect(marshall(value)).toEqual(convertToAttr(value));
45+
});
46+
});
47+
48+
describe("unwraps one level for input data which are lists or maps", () => {
49+
it("marshals and unwraps map", () => {
50+
expect(marshall({ a: 1, b: { a: 2, b: [1, 2, 3] } })).toEqual({
51+
a: { N: "1" },
52+
b: {
53+
M: {
54+
a: { N: "2" },
55+
b: {
56+
L: [{ N: "1" }, { N: "2" }, { N: "3" }],
57+
},
58+
},
59+
},
60+
});
61+
});
62+
63+
it("marshals and unwraps list", () => {
64+
expect(marshall(["test", 2, null])).toEqual([{ S: "test" }, { N: "2" }, { NULL: true }]);
65+
});
66+
});
67+
});

0 commit comments

Comments
 (0)