From 2fe0a883f8e5bb8ab855874072be62bc07441762 Mon Sep 17 00:00:00 2001 From: George Fu Date: Fri, 6 Oct 2023 15:52:42 -0400 Subject: [PATCH] fix(lib-dynamodb): add e2e suite and bug fixes for lib-dynamodb (#5306) * fix(lib-dynamodb): add e2e suite for lib-dynamodb * fix(lib-dynamodb): passing e2e tests * fix(lib-dynamodb): fix unit tests * fix(lib-dynamodb): make utils.spec non-generated * fix(lib-dynamodb): add additional e2e tests * fix: remove unused imports * test: more e2e cases * fix(lib-dynamodb): e2e scenarios WIP * fix(lib-dynamodb): use more readable keynode format * fix: update packages/util-dynamodb/src/unmarshall.ts Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> * fix(lib-dynamodb): use random table name and drop table after test --------- Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> --- .../codegen/AddDocumentClientPlugin.java | 14 - .../DocumentClientCommandGenerator.java | 53 +- .../codegen/doc-client-utils.spec.ts | 144 ----- .../typescript/codegen/doc-client-utils.ts | 60 -- lib/lib-dynamodb/jest.config.e2e.js | 5 + lib/lib-dynamodb/package.json | 3 +- .../DynamoDBDocumentClientCommand.spec.ts | 6 +- .../DynamoDBDocumentClientCommand.ts | 11 +- .../commands/BatchExecuteStatementCommand.ts | 22 +- .../src/commands/BatchGetCommand.ts | 33 +- .../src/commands/BatchWriteCommand.ts | 52 +- .../src/commands/DeleteCommand.ts | 27 +- .../src/commands/ExecuteStatementCommand.ts | 12 +- .../src/commands/ExecuteTransactionCommand.ts | 17 +- lib/lib-dynamodb/src/commands/GetCommand.ts | 9 +- lib/lib-dynamodb/src/commands/PutCommand.ts | 27 +- lib/lib-dynamodb/src/commands/QueryCommand.ts | 30 +- lib/lib-dynamodb/src/commands/ScanCommand.ts | 23 +- .../src/commands/TransactGetCommand.ts | 19 +- .../src/commands/TransactWriteCommand.ts | 45 +- .../src/commands/UpdateCommand.ts | 34 +- .../src/commands/marshallInput.spec.ts | 50 +- lib/lib-dynamodb/src/commands/utils.spec.ts | 129 ++-- lib/lib-dynamodb/src/commands/utils.ts | 105 +++- .../src/test/lib-dynamodb.e2e.spec.ts | 570 ++++++++++++++++++ packages/util-dynamodb/src/convertToAttr.ts | 6 +- packages/util-dynamodb/src/convertToNative.ts | 4 +- packages/util-dynamodb/src/marshall.ts | 14 +- packages/util-dynamodb/src/unmarshall.ts | 24 +- scripts/generate-clients/single-service.js | 10 + 30 files changed, 1060 insertions(+), 498 deletions(-) delete mode 100644 codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.spec.ts delete mode 100644 codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.ts create mode 100644 lib/lib-dynamodb/jest.config.e2e.js create mode 100644 lib/lib-dynamodb/src/test/lib-dynamodb.e2e.spec.ts diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDocumentClientPlugin.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDocumentClientPlugin.java index 941ee73e494f..2e06fdc516d2 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDocumentClientPlugin.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddDocumentClientPlugin.java @@ -33,7 +33,6 @@ import software.amazon.smithy.typescript.codegen.TypeScriptSettings; import software.amazon.smithy.typescript.codegen.TypeScriptWriter; import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration; -import software.amazon.smithy.utils.IoUtils; import software.amazon.smithy.utils.SmithyInternalApi; /** @@ -125,19 +124,6 @@ private void writeAdditionalFiles( writer.write("export * from './$L';", DocumentClientUtils.CLIENT_NAME); writer.write("export * from './$L';", DocumentClientUtils.CLIENT_FULL_NAME); }); - - String utilsFileLocation = String.format("%s%s", DocumentClientUtils.DOC_CLIENT_PREFIX, - DocumentClientUtils.CLIENT_UTILS_FILE); - writerFactory.accept(String.format("%s%s/%s.ts", DocumentClientUtils.DOC_CLIENT_PREFIX, - DocumentClientUtils.CLIENT_COMMANDS_FOLDER, DocumentClientUtils.CLIENT_UTILS_FILE), writer -> { - writer.write(IoUtils.readUtf8Resource(AddDocumentClientPlugin.class, - String.format("%s.ts", utilsFileLocation))); - }); - writerFactory.accept(String.format("%s%s/%s.spec.ts", DocumentClientUtils.DOC_CLIENT_PREFIX, - DocumentClientUtils.CLIENT_COMMANDS_FOLDER, DocumentClientUtils.CLIENT_UTILS_FILE), writer -> { - writer.write(IoUtils.readUtf8Resource(AddDocumentClientPlugin.class, - String.format("%s.spec.ts", utilsFileLocation))); - }); } } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/DocumentClientCommandGenerator.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/DocumentClientCommandGenerator.java index 947ffbf2feb0..4815634a0662 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/DocumentClientCommandGenerator.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/DocumentClientCommandGenerator.java @@ -134,10 +134,10 @@ public void run() { () -> { // Section for adding custom command properties. writer.pushState(COMMAND_PROPERTIES_SECTION); - writer.openBlock("protected readonly $L = [", "];", COMMAND_INPUT_KEYNODES, () -> { + writer.openBlock("protected readonly $L = {", "};", COMMAND_INPUT_KEYNODES, () -> { writeKeyNodes(inputMembersWithAttr); }); - writer.openBlock("protected readonly $L = [", "];", COMMAND_OUTPUT_KEYNODES, () -> { + writer.openBlock("protected readonly $L = {", "};", COMMAND_OUTPUT_KEYNODES, () -> { writeKeyNodes(outputMembersWithAttr); }); writer.popState(); @@ -220,7 +220,7 @@ private void generateCommandMiddlewareResolver(String configType) { private void writeKeyNodes(List membersWithAttr) { for (MemberShape member: membersWithAttr) { - writer.openBlock("{key: '$L', ", "},", symbolProvider.toMemberName(member), () -> { + writer.openBlock("'$L': ", ",", symbolProvider.toMemberName(member), () -> { writeKeyNode(member); }); } @@ -230,9 +230,21 @@ private void writeKeyNode(MemberShape member) { Shape memberTarget = model.expectShape(member.getTarget()); if (memberTarget instanceof CollectionShape) { MemberShape collectionMember = ((CollectionShape) memberTarget).getMember(); - writeKeyNode(collectionMember); + Shape collectionMemberTarget = model.expectShape(collectionMember.getTarget()); + if (collectionMemberTarget.isUnionShape() + && symbolProvider.toSymbol(collectionMemberTarget).getName().equals("AttributeValue")) { + writer.addImport("ALL_MEMBERS", null, "../src/commands/utils"); + writer.write("ALL_MEMBERS // set/list of AttributeValue"); + return; + } + writer.openBlock("{", "}", () -> { + writer.write("'*':"); + writeKeyNode(collectionMember); + }); } else if (memberTarget.isUnionShape()) { if (symbolProvider.toSymbol(memberTarget).getName().equals("AttributeValue")) { + writer.addImport("SELF", null, "../src/commands/utils"); + writer.write("SELF"); return; } else { // An AttributeValue inside Union is not present as of Q1 2021, and is less @@ -241,29 +253,30 @@ private void writeKeyNode(MemberShape member) { "AttributeValue inside Union is not supported, attempted for %s", memberTarget.getType() )); } - } else { - if (memberTarget.isMapShape()) { - MemberShape mapMember = ((MapShape) memberTarget).getValue(); - Shape mapMemberTarget = model.expectShape(mapMember.getTarget()); - if (mapMemberTarget.isUnionShape() - && symbolProvider.toSymbol(mapMemberTarget).getName().equals("AttributeValue")) { - return; - } else { - writer.openBlock("children: {", "},", () -> { - writeKeyNode(mapMember); - }); - } - } else if (memberTarget.isStructureShape()) { - writeStructureKeyNode((StructureShape) memberTarget); + } else if (memberTarget.isMapShape()) { + MemberShape mapMember = ((MapShape) memberTarget).getValue(); + Shape mapMemberTarget = model.expectShape(mapMember.getTarget()); + if (mapMemberTarget.isUnionShape() + && symbolProvider.toSymbol(mapMemberTarget).getName().equals("AttributeValue")) { + writer.addImport("ALL_VALUES", null, "../src/commands/utils"); + writer.write("ALL_VALUES // map with AttributeValue"); + return; + } else { + writer.openBlock("{", "}", () -> { + writer.write("'*':"); + writeKeyNode(mapMember); + }); } + } else if (memberTarget.isStructureShape()) { + writeStructureKeyNode((StructureShape) memberTarget); } } private void writeStructureKeyNode(StructureShape structureTarget) { List membersWithAttr = getStructureMembersWithAttr(Optional.of(structureTarget)); - writer.openBlock("children: [", "],", () -> { + writer.openBlock("{", "}", () -> { for (MemberShape member: membersWithAttr) { - writer.openBlock("{key: '$L', ", "},", symbolProvider.toMemberName(member), () -> { + writer.openBlock("'$L': ", ",", symbolProvider.toMemberName(member), () -> { writeKeyNode(member); }); } diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.spec.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.spec.ts deleted file mode 100644 index aaff6ed98d14..000000000000 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.spec.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { marshall, unmarshall } from "@aws-sdk/util-dynamodb"; - -import { marshallInput, unmarshallOutput } from "./utils"; - -jest.mock("@aws-sdk/util-dynamodb"); - -describe("utils", () => { - const notAttrValue = { NotAttrValue: "NotAttrValue" }; - - const attrValue = (num: number) => ({ id: { N: num } }); - const nativeAttrValue = (num: number) => ({ id: num }); - - const testCases = [ - { - testName: "single key", - keyNodes: [{ key: "Item" }], - attrObj: { Item: attrValue(1), ...notAttrValue }, - nativeAttrObj: { Item: nativeAttrValue(1), ...notAttrValue }, - processCalledTimes: 1, - }, - { - testName: "multiple keys", - keyNodes: [{ key: "Item1" }, { key: "Item2" }], - attrObj: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, - nativeAttrObj: { Item1: nativeAttrValue(1), Item2: nativeAttrValue(2), ...notAttrValue }, - processCalledTimes: 2, - }, - { - testName: "array", - keyNodes: [{ key: "Items" }], - attrObj: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, - nativeAttrObj: { Items: [nativeAttrValue(1), nativeAttrValue(2)], ...notAttrValue }, - processCalledTimes: 2, - }, - { - testName: "secondary level", - keyNodes: [{ key: "Parent", children: [{ key: "Item" }] }], - attrObj: { Parent: { Item: attrValue(1), ...notAttrValue }, ...notAttrValue }, - nativeAttrObj: { Parent: { Item: nativeAttrValue(1), ...notAttrValue }, ...notAttrValue }, - processCalledTimes: 1, - }, - { - testName: "secondary level array", - keyNodes: [{ key: "Parent", children: [{ key: "Items" }] }], - attrObj: { Parent: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, ...notAttrValue }, - nativeAttrObj: { Parent: { Items: [nativeAttrValue(1), nativeAttrValue(2)], ...notAttrValue }, ...notAttrValue }, - processCalledTimes: 2, - }, - { - testName: "all entries", - keyNodes: [{ key: "Parent", children: {} }], - attrObj: { Parent: { key1: attrValue(1), key2: attrValue(2) }, ...notAttrValue }, - nativeAttrObj: { Parent: { key1: nativeAttrValue(1), key2: nativeAttrValue(2) }, ...notAttrValue }, - processCalledTimes: 2, - }, - { - testName: "all entries single key", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Item" }] } }], - attrObj: { - Parent: { - key1: { Item: attrValue(1), ...notAttrValue }, - key2: { Item: attrValue(2), ...notAttrValue }, - }, - ...notAttrValue, - }, - nativeAttrObj: { - Parent: { - key1: { Item: nativeAttrValue(1), ...notAttrValue }, - key2: { Item: nativeAttrValue(2), ...notAttrValue }, - }, - ...notAttrValue, - }, - processCalledTimes: 2, - }, - { - testName: "all entries multiple keys", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Item1" }, { key: "Item2" }] } }], - attrObj: { - Parent: { - key1: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, - key2: { Item1: attrValue(3), Item2: attrValue(4), ...notAttrValue }, - }, - ...notAttrValue, - }, - nativeAttrObj: { - Parent: { - key1: { Item1: nativeAttrValue(1), Item2: nativeAttrValue(2), ...notAttrValue }, - key2: { Item1: nativeAttrValue(3), Item2: nativeAttrValue(4), ...notAttrValue }, - }, - ...notAttrValue, - }, - processCalledTimes: 4, - }, - { - testName: "all entries array", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Items" }] } }], - attrObj: { - Parent: { - key1: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, - key2: { Items: [attrValue(3), attrValue(4)], ...notAttrValue }, - }, - ...notAttrValue, - }, - nativeAttrObj: { - Parent: { - key1: { Items: [nativeAttrValue(1), nativeAttrValue(2)], ...notAttrValue }, - key2: { Items: [nativeAttrValue(3), nativeAttrValue(4)], ...notAttrValue }, - }, - ...notAttrValue, - }, - processCalledTimes: 4, - }, - ]; - - afterEach(() => { - jest.clearAllMocks(); - }); - - testCases.forEach(({ testName, keyNodes, attrObj, nativeAttrObj, processCalledTimes }) => { - describe(testName, () => { - it(marshallInput.name, () => { - for (let i = 1; i <= processCalledTimes; i++) { - (marshall as jest.Mock).mockReturnValueOnce(attrValue(i)); - } - expect(marshallInput(nativeAttrObj, keyNodes)).toEqual(attrObj); - expect(marshall).toHaveBeenCalledTimes(processCalledTimes); - for (let i = 1; i <= processCalledTimes; i++) { - expect(marshall).toHaveBeenNthCalledWith(i, nativeAttrValue(i), undefined); - } - }); - - it(unmarshallOutput.name, () => { - for (let i = 1; i <= processCalledTimes; i++) { - (unmarshall as jest.Mock).mockReturnValueOnce(nativeAttrValue(i)); - } - expect(unmarshallOutput(attrObj, keyNodes)).toEqual(nativeAttrObj); - expect(unmarshall).toHaveBeenCalledTimes(processCalledTimes); - for (let i = 1; i <= processCalledTimes; i++) { - expect(unmarshall).toHaveBeenNthCalledWith(i, attrValue(i), undefined); - } - }); - }); - }); -}); diff --git a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.ts b/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.ts deleted file mode 100644 index 9e6dc5b44901..000000000000 --- a/codegen/smithy-aws-typescript-codegen/src/main/resources/software/amazon/smithy/aws/typescript/codegen/doc-client-utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { marshall, marshallOptions, unmarshall, unmarshallOptions } from "@aws-sdk/util-dynamodb"; - -export type KeyNode = { - key: string; - children?: KeyNode[] | AllNodes; -}; - -export type AllNodes = { - children?: KeyNode[] | AllNodes; -}; - -const processObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => { - if (obj !== undefined) { - if (!children || (Array.isArray(children) && children.length === 0)) { - // Leaf of KeyNode, process the object. - return processFunc(obj); - } else { - // Not leaf node, process the children. - if (Array.isArray(children)) { - // Specific keys of children need to be processed. - return processKeysInObj(obj, processFunc, children); - } else { - // All children require processing. - return processAllKeysInObj(obj, processFunc, children.children); - } - } - } - return undefined; -}; - -const processKeyInObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => { - if (Array.isArray(obj)) { - return obj.map((item: any) => processObj(item, processFunc, children)); - } - return processObj(obj, processFunc, children); -}; - -const processKeysInObj = (obj: any, processFunc: Function, keyNodes: KeyNode[]) => { - const accumulator = { ...obj }; - return keyNodes.reduce((acc, { key, children }) => { - acc[key] = processKeyInObj(acc[key], processFunc, children); - return acc; - }, accumulator); -}; - -const processAllKeysInObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => - Object.entries(obj).reduce((acc, [key, value]) => { - acc[key] = processKeyInObj(value, processFunc, children); - return acc; - }, {} as any); - -export const marshallInput = (obj: any, keyNodes: KeyNode[], options?: marshallOptions) => { - const marshallFunc = (toMarshall: any) => marshall(toMarshall, options); - return processKeysInObj(obj, marshallFunc, keyNodes); -}; - -export const unmarshallOutput = (obj: any, keyNodes: KeyNode[], options?: unmarshallOptions) => { - const unmarshallFunc = (toMarshall: any) => unmarshall(toMarshall, options); - return processKeysInObj(obj, unmarshallFunc, keyNodes); -}; diff --git a/lib/lib-dynamodb/jest.config.e2e.js b/lib/lib-dynamodb/jest.config.e2e.js new file mode 100644 index 000000000000..c3aa6055ef75 --- /dev/null +++ b/lib/lib-dynamodb/jest.config.e2e.js @@ -0,0 +1,5 @@ +module.exports = { + preset: "ts-jest", + testMatch: ["**/*.e2e.spec.ts"], + bail: true, +}; diff --git a/lib/lib-dynamodb/package.json b/lib/lib-dynamodb/package.json index cffa1f44ca47..265156583f2d 100644 --- a/lib/lib-dynamodb/package.json +++ b/lib/lib-dynamodb/package.json @@ -14,7 +14,8 @@ "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4", "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo", "extract:docs": "api-extractor run --local", - "test": "jest" + "test": "jest", + "test:e2e": "jest --config jest.config.e2e.js" }, "engines": { "node": ">=14.0.0" diff --git a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.spec.ts b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.spec.ts index 427038392ec3..fbb60ba2acca 100644 --- a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.spec.ts +++ b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.spec.ts @@ -1,13 +1,13 @@ import { Handler, MiddlewareStack } from "@smithy/types"; -import { KeyNode } from "../commands/utils"; +import { KeyNodeChildren } from "../commands/utils"; import { DynamoDBDocumentClientCommand } from "./DynamoDBDocumentClientCommand"; class AnyCommand extends DynamoDBDocumentClientCommand<{}, {}, {}, {}, {}> { public middlewareStack: MiddlewareStack<{}, {}>; public input: {}; - protected inputKeyNodes: KeyNode[] = []; - protected outputKeyNodes: KeyNode[] = []; + protected inputKeyNodes: KeyNodeChildren = {}; + protected outputKeyNodes: KeyNodeChildren = {}; public argCaptor: [Function, object][] = []; diff --git a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts index 5e7f54ef5d67..94cf791d1488 100644 --- a/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts +++ b/lib/lib-dynamodb/src/baseCommand/DynamoDBDocumentClientCommand.ts @@ -10,7 +10,7 @@ import { MiddlewareStack, } from "@smithy/types"; -import { KeyNode, marshallInput, unmarshallOutput } from "../commands/utils"; +import { KeyNodeChildren, marshallInput, unmarshallOutput } from "../commands/utils"; import { DynamoDBDocumentClientResolvedConfig } from "../DynamoDBDocumentClient"; // /** @public */ @@ -29,8 +29,8 @@ export abstract class DynamoDBDocumentClientCommand< BaseOutput extends object, ResolvedClientConfiguration > extends $Command { - protected abstract readonly inputKeyNodes: KeyNode[]; - protected abstract readonly outputKeyNodes: KeyNode[]; + protected abstract readonly inputKeyNodes: KeyNodeChildren; + protected abstract readonly outputKeyNodes: KeyNodeChildren; protected abstract clientCommand: $Command; public abstract middlewareStack: MiddlewareStack; @@ -41,7 +41,10 @@ export abstract class DynamoDBDocumentClientCommand< }; protected addMarshallingMiddleware(configuration: DynamoDBDocumentClientResolvedConfig): void { - const { marshallOptions, unmarshallOptions } = configuration.translateConfig || {}; + const { marshallOptions = {}, unmarshallOptions = {} } = configuration.translateConfig || {}; + + marshallOptions.convertTopLevelContainer = marshallOptions.convertTopLevelContainer ?? true; + unmarshallOptions.convertWithoutMapWrapper = unmarshallOptions.convertWithoutMapWrapper ?? true; this.clientCommand.middlewareStack.addRelativeTo( (next: InitializeHandler, context: HandlerExecutionContext) => diff --git a/lib/lib-dynamodb/src/commands/BatchExecuteStatementCommand.ts b/lib/lib-dynamodb/src/commands/BatchExecuteStatementCommand.ts index c5921850556b..3e7ba0e7b168 100644 --- a/lib/lib-dynamodb/src/commands/BatchExecuteStatementCommand.ts +++ b/lib/lib-dynamodb/src/commands/BatchExecuteStatementCommand.ts @@ -11,6 +11,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -58,10 +59,23 @@ export class BatchExecuteStatementCommand extends DynamoDBDocumentClientCommand< __BatchExecuteStatementCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [{ key: "Statements", children: [{ key: "Parameters" }] }]; - protected readonly outputKeyNodes = [ - { key: "Responses", children: [{ key: "Error", children: [{ key: "Item" }] }, { key: "Item" }] }, - ]; + protected readonly inputKeyNodes = { + Statements: { + "*": { + Parameters: ALL_MEMBERS, // set/list of AttributeValue + }, + }, + }; + protected readonly outputKeyNodes = { + Responses: { + "*": { + Error: { + Item: ALL_VALUES, // map with AttributeValue + }, + Item: ALL_VALUES, // map with AttributeValue + }, + }, + }; protected readonly clientCommand: __BatchExecuteStatementCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/BatchGetCommand.ts b/lib/lib-dynamodb/src/commands/BatchGetCommand.ts index 6e6bd12a8cce..2d2c7b1d6bfc 100644 --- a/lib/lib-dynamodb/src/commands/BatchGetCommand.ts +++ b/lib/lib-dynamodb/src/commands/BatchGetCommand.ts @@ -9,6 +9,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -60,23 +61,29 @@ export class BatchGetCommand extends DynamoDBDocumentClientCommand< __BatchGetItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { - key: "RequestItems", - children: { - children: [{ key: "Keys" }], + protected readonly inputKeyNodes = { + RequestItems: { + "*": { + Keys: { + "*": ALL_VALUES, // map with AttributeValue + }, }, }, - ]; - protected readonly outputKeyNodes = [ - { key: "Responses", children: {} }, - { - key: "UnprocessedKeys", - children: { - children: [{ key: "Keys" }], + }; + protected readonly outputKeyNodes = { + Responses: { + "*": { + "*": ALL_VALUES, // map with AttributeValue }, }, - ]; + UnprocessedKeys: { + "*": { + Keys: { + "*": ALL_VALUES, // map with AttributeValue + }, + }, + }, + }; protected readonly clientCommand: __BatchGetItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/BatchWriteCommand.ts b/lib/lib-dynamodb/src/commands/BatchWriteCommand.ts index 350ea92f7f47..da06cac6d9c6 100644 --- a/lib/lib-dynamodb/src/commands/BatchWriteCommand.ts +++ b/lib/lib-dynamodb/src/commands/BatchWriteCommand.ts @@ -12,6 +12,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -81,34 +82,41 @@ export class BatchWriteCommand extends DynamoDBDocumentClientCommand< __BatchWriteItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { - key: "RequestItems", - children: { - children: [ - { key: "PutRequest", children: [{ key: "Item" }] }, - { key: "DeleteRequest", children: [{ key: "Key" }] }, - ], + protected readonly inputKeyNodes = { + RequestItems: { + "*": { + "*": { + PutRequest: { + Item: ALL_VALUES, // map with AttributeValue + }, + DeleteRequest: { + Key: ALL_VALUES, // map with AttributeValue + }, + }, }, }, - ]; - protected readonly outputKeyNodes = [ - { - key: "UnprocessedItems", - children: { - children: [ - { key: "PutRequest", children: [{ key: "Item" }] }, - { key: "DeleteRequest", children: [{ key: "Key" }] }, - ], + }; + protected readonly outputKeyNodes = { + UnprocessedItems: { + "*": { + "*": { + PutRequest: { + Item: ALL_VALUES, // map with AttributeValue + }, + DeleteRequest: { + Key: ALL_VALUES, // map with AttributeValue + }, + }, }, }, - { - key: "ItemCollectionMetrics", - children: { - children: [{ key: "ItemCollectionKey" }], + ItemCollectionMetrics: { + "*": { + "*": { + ItemCollectionKey: ALL_VALUES, // map with AttributeValue + }, }, }, - ]; + }; protected readonly clientCommand: __BatchWriteItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/DeleteCommand.ts b/lib/lib-dynamodb/src/commands/DeleteCommand.ts index 9adb79dcea0d..4399439fe1b1 100644 --- a/lib/lib-dynamodb/src/commands/DeleteCommand.ts +++ b/lib/lib-dynamodb/src/commands/DeleteCommand.ts @@ -10,6 +10,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES, SELF } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -59,20 +60,22 @@ export class DeleteCommand extends DynamoDBDocumentClientCommand< __DeleteItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { key: "Key" }, - { - key: "Expected", - children: { - children: [{ key: "Value" }, { key: "AttributeValueList" }], + protected readonly inputKeyNodes = { + Key: ALL_VALUES, // map with AttributeValue + Expected: { + "*": { + Value: SELF, + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { key: "ExpressionAttributeValues" }, - ]; - protected readonly outputKeyNodes = [ - { key: "Attributes" }, - { key: "ItemCollectionMetrics", children: [{ key: "ItemCollectionKey" }] }, - ]; + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Attributes: ALL_VALUES, // map with AttributeValue + ItemCollectionMetrics: { + ItemCollectionKey: ALL_VALUES, // map with AttributeValue + }, + }; protected readonly clientCommand: __DeleteItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/ExecuteStatementCommand.ts b/lib/lib-dynamodb/src/commands/ExecuteStatementCommand.ts index 937dfead31bc..4dcdbcf7a67e 100644 --- a/lib/lib-dynamodb/src/commands/ExecuteStatementCommand.ts +++ b/lib/lib-dynamodb/src/commands/ExecuteStatementCommand.ts @@ -8,6 +8,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -47,8 +48,15 @@ export class ExecuteStatementCommand extends DynamoDBDocumentClientCommand< __ExecuteStatementCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [{ key: "Parameters" }]; - protected readonly outputKeyNodes = [{ key: "Items" }, { key: "LastEvaluatedKey" }]; + protected readonly inputKeyNodes = { + Parameters: ALL_MEMBERS, // set/list of AttributeValue + }; + protected readonly outputKeyNodes = { + Items: { + "*": ALL_VALUES, // map with AttributeValue + }, + LastEvaluatedKey: ALL_VALUES, // map with AttributeValue + }; protected readonly clientCommand: __ExecuteStatementCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/ExecuteTransactionCommand.ts b/lib/lib-dynamodb/src/commands/ExecuteTransactionCommand.ts index afea40d815c1..f74575239d2b 100644 --- a/lib/lib-dynamodb/src/commands/ExecuteTransactionCommand.ts +++ b/lib/lib-dynamodb/src/commands/ExecuteTransactionCommand.ts @@ -10,6 +10,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -54,8 +55,20 @@ export class ExecuteTransactionCommand extends DynamoDBDocumentClientCommand< __ExecuteTransactionCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [{ key: "TransactStatements", children: [{ key: "Parameters" }] }]; - protected readonly outputKeyNodes = [{ key: "Responses", children: [{ key: "Item" }] }]; + protected readonly inputKeyNodes = { + TransactStatements: { + "*": { + Parameters: ALL_MEMBERS, // set/list of AttributeValue + }, + }, + }; + protected readonly outputKeyNodes = { + Responses: { + "*": { + Item: ALL_VALUES, // map with AttributeValue + }, + }, + }; protected readonly clientCommand: __ExecuteTransactionCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/GetCommand.ts b/lib/lib-dynamodb/src/commands/GetCommand.ts index b30b985ffcad..4f2dd07b61f1 100644 --- a/lib/lib-dynamodb/src/commands/GetCommand.ts +++ b/lib/lib-dynamodb/src/commands/GetCommand.ts @@ -8,6 +8,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -46,8 +47,12 @@ export class GetCommand extends DynamoDBDocumentClientCommand< __GetItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [{ key: "Key" }]; - protected readonly outputKeyNodes = [{ key: "Item" }]; + protected readonly inputKeyNodes = { + Key: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Item: ALL_VALUES, // map with AttributeValue + }; protected readonly clientCommand: __GetItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/PutCommand.ts b/lib/lib-dynamodb/src/commands/PutCommand.ts index 1ffbb3456d85..920000ed3072 100644 --- a/lib/lib-dynamodb/src/commands/PutCommand.ts +++ b/lib/lib-dynamodb/src/commands/PutCommand.ts @@ -10,6 +10,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES, SELF } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -59,20 +60,22 @@ export class PutCommand extends DynamoDBDocumentClientCommand< __PutItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { key: "Item" }, - { - key: "Expected", - children: { - children: [{ key: "Value" }, { key: "AttributeValueList" }], + protected readonly inputKeyNodes = { + Item: ALL_VALUES, // map with AttributeValue + Expected: { + "*": { + Value: SELF, + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { key: "ExpressionAttributeValues" }, - ]; - protected readonly outputKeyNodes = [ - { key: "Attributes" }, - { key: "ItemCollectionMetrics", children: [{ key: "ItemCollectionKey" }] }, - ]; + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Attributes: ALL_VALUES, // map with AttributeValue + ItemCollectionMetrics: { + ItemCollectionKey: ALL_VALUES, // map with AttributeValue + }, + }; protected readonly clientCommand: __PutItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/QueryCommand.ts b/lib/lib-dynamodb/src/commands/QueryCommand.ts index 829a580a12b1..80003eb6de31 100644 --- a/lib/lib-dynamodb/src/commands/QueryCommand.ts +++ b/lib/lib-dynamodb/src/commands/QueryCommand.ts @@ -9,6 +9,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -64,23 +65,26 @@ export class QueryCommand extends DynamoDBDocumentClientCommand< __QueryCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { - key: "KeyConditions", - children: { - children: [{ key: "AttributeValueList" }], + protected readonly inputKeyNodes = { + KeyConditions: { + "*": { + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { - key: "QueryFilter", - children: { - children: [{ key: "AttributeValueList" }], + QueryFilter: { + "*": { + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { key: "ExclusiveStartKey" }, - { key: "ExpressionAttributeValues" }, - ]; - protected readonly outputKeyNodes = [{ key: "Items" }, { key: "LastEvaluatedKey" }]; + ExclusiveStartKey: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Items: { + "*": ALL_VALUES, // map with AttributeValue + }, + LastEvaluatedKey: ALL_VALUES, // map with AttributeValue + }; protected readonly clientCommand: __QueryCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/ScanCommand.ts b/lib/lib-dynamodb/src/commands/ScanCommand.ts index 3a29809c639b..b4c89e45aeef 100644 --- a/lib/lib-dynamodb/src/commands/ScanCommand.ts +++ b/lib/lib-dynamodb/src/commands/ScanCommand.ts @@ -9,6 +9,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -58,17 +59,21 @@ export class ScanCommand extends DynamoDBDocumentClientCommand< __ScanCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { - key: "ScanFilter", - children: { - children: [{ key: "AttributeValueList" }], + protected readonly inputKeyNodes = { + ScanFilter: { + "*": { + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { key: "ExclusiveStartKey" }, - { key: "ExpressionAttributeValues" }, - ]; - protected readonly outputKeyNodes = [{ key: "Items" }, { key: "LastEvaluatedKey" }]; + ExclusiveStartKey: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Items: { + "*": ALL_VALUES, // map with AttributeValue + }, + LastEvaluatedKey: ALL_VALUES, // map with AttributeValue + }; protected readonly clientCommand: __ScanCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/TransactGetCommand.ts b/lib/lib-dynamodb/src/commands/TransactGetCommand.ts index 071fc0ac577a..c4b7a3792845 100644 --- a/lib/lib-dynamodb/src/commands/TransactGetCommand.ts +++ b/lib/lib-dynamodb/src/commands/TransactGetCommand.ts @@ -11,6 +11,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -59,8 +60,22 @@ export class TransactGetCommand extends DynamoDBDocumentClientCommand< __TransactGetItemsCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [{ key: "TransactItems", children: [{ key: "Get", children: [{ key: "Key" }] }] }]; - protected readonly outputKeyNodes = [{ key: "Responses", children: [{ key: "Item" }] }]; + protected readonly inputKeyNodes = { + TransactItems: { + "*": { + Get: { + Key: ALL_VALUES, // map with AttributeValue + }, + }, + }, + }; + protected readonly outputKeyNodes = { + Responses: { + "*": { + Item: ALL_VALUES, // map with AttributeValue + }, + }, + }; protected readonly clientCommand: __TransactGetItemsCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/TransactWriteCommand.ts b/lib/lib-dynamodb/src/commands/TransactWriteCommand.ts index 5cc98b3f83c4..36a6e86af05d 100644 --- a/lib/lib-dynamodb/src/commands/TransactWriteCommand.ts +++ b/lib/lib-dynamodb/src/commands/TransactWriteCommand.ts @@ -14,6 +14,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_VALUES } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -76,25 +77,37 @@ export class TransactWriteCommand extends DynamoDBDocumentClientCommand< __TransactWriteItemsCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { - key: "TransactItems", - children: [ - { key: "ConditionCheck", children: [{ key: "Key" }, { key: "ExpressionAttributeValues" }] }, - { key: "Put", children: [{ key: "Item" }, { key: "ExpressionAttributeValues" }] }, - { key: "Delete", children: [{ key: "Key" }, { key: "ExpressionAttributeValues" }] }, - { key: "Update", children: [{ key: "Key" }, { key: "ExpressionAttributeValues" }] }, - ], + protected readonly inputKeyNodes = { + TransactItems: { + "*": { + ConditionCheck: { + Key: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }, + Put: { + Item: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }, + Delete: { + Key: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }, + Update: { + Key: ALL_VALUES, // map with AttributeValue + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }, + }, }, - ]; - protected readonly outputKeyNodes = [ - { - key: "ItemCollectionMetrics", - children: { - children: [{ key: "ItemCollectionKey" }], + }; + protected readonly outputKeyNodes = { + ItemCollectionMetrics: { + "*": { + "*": { + ItemCollectionKey: ALL_VALUES, // map with AttributeValue + }, }, }, - ]; + }; protected readonly clientCommand: __TransactWriteItemsCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/UpdateCommand.ts b/lib/lib-dynamodb/src/commands/UpdateCommand.ts index a52475c64348..add23bdb9539 100644 --- a/lib/lib-dynamodb/src/commands/UpdateCommand.ts +++ b/lib/lib-dynamodb/src/commands/UpdateCommand.ts @@ -11,6 +11,7 @@ import { NativeAttributeValue } from "@aws-sdk/util-dynamodb"; import { Command as $Command } from "@smithy/smithy-client"; import { Handler, HttpHandlerOptions as __HttpHandlerOptions, MiddlewareStack } from "@smithy/types"; +import { ALL_MEMBERS, ALL_VALUES, SELF } from "../../src/commands/utils"; import { DynamoDBDocumentClientCommand } from "../baseCommand/DynamoDBDocumentClientCommand"; import { DynamoDBDocumentClientResolvedConfig, ServiceInputTypes, ServiceOutputTypes } from "../DynamoDBDocumentClient"; @@ -69,26 +70,27 @@ export class UpdateCommand extends DynamoDBDocumentClientCommand< __UpdateItemCommandOutput, DynamoDBDocumentClientResolvedConfig > { - protected readonly inputKeyNodes = [ - { key: "Key" }, - { - key: "AttributeUpdates", - children: { - children: [{ key: "Value" }], + protected readonly inputKeyNodes = { + Key: ALL_VALUES, // map with AttributeValue + AttributeUpdates: { + "*": { + Value: SELF, }, }, - { - key: "Expected", - children: { - children: [{ key: "Value" }, { key: "AttributeValueList" }], + Expected: { + "*": { + Value: SELF, + AttributeValueList: ALL_MEMBERS, // set/list of AttributeValue }, }, - { key: "ExpressionAttributeValues" }, - ]; - protected readonly outputKeyNodes = [ - { key: "Attributes" }, - { key: "ItemCollectionMetrics", children: [{ key: "ItemCollectionKey" }] }, - ]; + ExpressionAttributeValues: ALL_VALUES, // map with AttributeValue + }; + protected readonly outputKeyNodes = { + Attributes: ALL_VALUES, // map with AttributeValue + ItemCollectionMetrics: { + ItemCollectionKey: ALL_VALUES, // map with AttributeValue + }, + }; protected readonly clientCommand: __UpdateItemCommand; public readonly middlewareStack: MiddlewareStack< diff --git a/lib/lib-dynamodb/src/commands/marshallInput.spec.ts b/lib/lib-dynamodb/src/commands/marshallInput.spec.ts index 17890ff2838b..c6df6f96e4e4 100644 --- a/lib/lib-dynamodb/src/commands/marshallInput.spec.ts +++ b/lib/lib-dynamodb/src/commands/marshallInput.spec.ts @@ -2,9 +2,13 @@ import { marshallInput } from "./utils"; describe("marshallInput and processObj", () => { it("marshallInput should not ignore falsy values", () => { - expect(marshallInput({ Items: [0, false, null, ""] }, [{ key: "Items" }])).toEqual({ - Items: [{ N: "0" }, { BOOL: false }, { NULL: true }, { S: "" }], - }); + expect(marshallInput({ Items: [0, false, null, ""] }, { Items: null }, { convertTopLevelContainer: true })).toEqual( + { + Items: { + L: [{ N: "0" }, { BOOL: false }, { NULL: true }, { S: "" }], + }, + } + ); }); }); @@ -19,22 +23,20 @@ describe("marshallInput for commands", () => { }, }, }; - const inputKeyNodes = [ - { - key: "KeyConditions", - children: { - children: [{ key: "AttributeValueList" }], + const inputKeyNodes = { + KeyConditions: { + "*": { + AttributeValueList: [], }, }, - { - key: "QueryFilter", - children: { - children: [{ key: "AttributeValueList" }], + QueryFilter: { + "*": { + AttributeValueList: null, }, }, - { key: "ExclusiveStartKey" }, - { key: "ExpressionAttributeValues" }, - ]; + ExclusiveStartKey: null, + ExpressionAttributeValues: null, + }; const output = { TableName: "TestTable", KeyConditions: { id: { AttributeValueList: [{ S: "test" }], ComparisonOperator: "EQ" } }, @@ -42,7 +44,7 @@ describe("marshallInput for commands", () => { ExclusiveStartKey: undefined, ExpressionAttributeValues: undefined, }; - expect(marshallInput(input, inputKeyNodes)).toEqual(output); + expect(marshallInput(input, inputKeyNodes, { convertTopLevelContainer: true })).toEqual(output); }); it("marshals ExecuteStatementCommand input", () => { const input = { @@ -51,12 +53,14 @@ describe("marshallInput for commands", () => { WHERE contains("col_1", ?)`, Parameters: ["some_param"], }; - const inputKeyNodes = [{ key: "Parameters" }]; + const inputKeyNodes = { + Parameters: [], + }; const output = { Statement: input.Statement, Parameters: [{ S: "some_param" }], }; - expect(marshallInput(input, inputKeyNodes)).toEqual(output); + expect(marshallInput(input, inputKeyNodes, { convertTopLevelContainer: true })).toEqual(output); }); it("marshals BatchExecuteStatementCommand input", () => { const input = { @@ -72,7 +76,13 @@ describe("marshallInput for commands", () => { }, ], }; - const inputKeyNodes = [{ key: "Statements", children: [{ key: "Parameters" }] }]; + const inputKeyNodes = { + Statements: { + "*": { + Parameters: [], + }, + }, + }; const output = { Statements: [ { @@ -87,6 +97,6 @@ describe("marshallInput for commands", () => { }, ], }; - expect(marshallInput(input, inputKeyNodes)).toEqual(output); + expect(marshallInput(input, inputKeyNodes, { convertTopLevelContainer: true })).toEqual(output); }); }); diff --git a/lib/lib-dynamodb/src/commands/utils.spec.ts b/lib/lib-dynamodb/src/commands/utils.spec.ts index cee2e0a966c2..dad1cfa939f8 100644 --- a/lib/lib-dynamodb/src/commands/utils.spec.ts +++ b/lib/lib-dynamodb/src/commands/utils.spec.ts @@ -1,68 +1,70 @@ -// smithy-typescript generated code -import { marshall, unmarshall } from "@aws-sdk/util-dynamodb"; - import { marshallInput, unmarshallOutput } from "./utils"; -jest.mock("@aws-sdk/util-dynamodb"); - describe("utils", () => { const notAttrValue = { NotAttrValue: "NotAttrValue" }; - const attrValue = (num: number) => ({ id: { N: num } }); + const attrValue = (num: number) => ({ id: { N: String(num) } }); const nativeAttrValue = (num: number) => ({ id: num }); const testCases = [ { testName: "single key", - keyNodes: [{ key: "Item" }], - attrObj: { Item: attrValue(1), ...notAttrValue }, + keyNodes: { Item: {} }, nativeAttrObj: { Item: nativeAttrValue(1), ...notAttrValue }, - processCalledTimes: 1, + attrObj: { Item: attrValue(1), ...notAttrValue }, }, { testName: "multiple keys", - keyNodes: [{ key: "Item1" }, { key: "Item2" }], - attrObj: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, + keyNodes: { Item1: {}, Item2: {} }, nativeAttrObj: { Item1: nativeAttrValue(1), Item2: nativeAttrValue(2), ...notAttrValue }, - processCalledTimes: 2, + attrObj: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, }, { testName: "array", - keyNodes: [{ key: "Items" }], - attrObj: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, + keyNodes: { Items: { "*": {} } }, nativeAttrObj: { Items: [nativeAttrValue(1), nativeAttrValue(2)], ...notAttrValue }, - processCalledTimes: 2, + attrObj: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, }, { testName: "secondary level", - keyNodes: [{ key: "Parent", children: [{ key: "Item" }] }], - attrObj: { Parent: { Item: attrValue(1), ...notAttrValue }, ...notAttrValue }, + keyNodes: { + Parent: { + Item: {}, + }, + }, nativeAttrObj: { Parent: { Item: nativeAttrValue(1), ...notAttrValue }, ...notAttrValue }, - processCalledTimes: 1, + attrObj: { Parent: { Item: attrValue(1), ...notAttrValue }, ...notAttrValue }, }, { testName: "secondary level array", - keyNodes: [{ key: "Parent", children: [{ key: "Items" }] }], - attrObj: { Parent: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, ...notAttrValue }, + keyNodes: { + Parent: { + Items: { + "*": [], + }, + }, + }, nativeAttrObj: { Parent: { Items: [nativeAttrValue(1), nativeAttrValue(2)], ...notAttrValue }, ...notAttrValue }, - processCalledTimes: 2, + attrObj: { Parent: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, ...notAttrValue }, }, { testName: "all entries", - keyNodes: [{ key: "Parent", children: {} }], - attrObj: { Parent: { key1: attrValue(1), key2: attrValue(2) }, ...notAttrValue }, + keyNodes: { + Parent: { + "*": {}, + }, + }, nativeAttrObj: { Parent: { key1: nativeAttrValue(1), key2: nativeAttrValue(2) }, ...notAttrValue }, - processCalledTimes: 2, + attrObj: { Parent: { key1: attrValue(1), key2: attrValue(2) }, ...notAttrValue }, }, { testName: "all entries single key", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Item" }] } }], - attrObj: { + keyNodes: { Parent: { - key1: { Item: attrValue(1), ...notAttrValue }, - key2: { Item: attrValue(2), ...notAttrValue }, + "*": { + Item: {}, + }, }, - ...notAttrValue, }, nativeAttrObj: { Parent: { @@ -71,17 +73,23 @@ describe("utils", () => { }, ...notAttrValue, }, - processCalledTimes: 2, + attrObj: { + Parent: { + key1: { Item: attrValue(1), ...notAttrValue }, + key2: { Item: attrValue(2), ...notAttrValue }, + }, + ...notAttrValue, + }, }, { testName: "all entries multiple keys", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Item1" }, { key: "Item2" }] } }], - attrObj: { + keyNodes: { Parent: { - key1: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, - key2: { Item1: attrValue(3), Item2: attrValue(4), ...notAttrValue }, + "*": { + Item1: {}, + Item2: {}, + }, }, - ...notAttrValue, }, nativeAttrObj: { Parent: { @@ -90,17 +98,24 @@ describe("utils", () => { }, ...notAttrValue, }, - processCalledTimes: 4, + attrObj: { + Parent: { + key1: { Item1: attrValue(1), Item2: attrValue(2), ...notAttrValue }, + key2: { Item1: attrValue(3), Item2: attrValue(4), ...notAttrValue }, + }, + ...notAttrValue, + }, }, { testName: "all entries array", - keyNodes: [{ key: "Parent", children: { children: [{ key: "Items" }] } }], - attrObj: { + keyNodes: { Parent: { - key1: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, - key2: { Items: [attrValue(3), attrValue(4)], ...notAttrValue }, + "*": { + Items: { + "*": [], + }, + }, }, - ...notAttrValue, }, nativeAttrObj: { Parent: { @@ -109,36 +124,24 @@ describe("utils", () => { }, ...notAttrValue, }, - processCalledTimes: 4, + attrObj: { + Parent: { + key1: { Items: [attrValue(1), attrValue(2)], ...notAttrValue }, + key2: { Items: [attrValue(3), attrValue(4)], ...notAttrValue }, + }, + ...notAttrValue, + }, }, ]; - afterEach(() => { - jest.clearAllMocks(); - }); - - testCases.forEach(({ testName, keyNodes, attrObj, nativeAttrObj, processCalledTimes }) => { + testCases.forEach(({ testName, keyNodes, attrObj, nativeAttrObj }) => { describe(testName, () => { it(marshallInput.name, () => { - for (let i = 1; i <= processCalledTimes; i++) { - (marshall as jest.Mock).mockReturnValueOnce(attrValue(i)); - } - expect(marshallInput(nativeAttrObj, keyNodes)).toEqual(attrObj); - expect(marshall).toHaveBeenCalledTimes(processCalledTimes); - for (let i = 1; i <= processCalledTimes; i++) { - expect(marshall).toHaveBeenNthCalledWith(i, nativeAttrValue(i), undefined); - } + expect(marshallInput(nativeAttrObj, keyNodes, { convertTopLevelContainer: true })).toEqual(attrObj); }); it(unmarshallOutput.name, () => { - for (let i = 1; i <= processCalledTimes; i++) { - (unmarshall as jest.Mock).mockReturnValueOnce(nativeAttrValue(i)); - } - expect(unmarshallOutput(attrObj, keyNodes)).toEqual(nativeAttrObj); - expect(unmarshall).toHaveBeenCalledTimes(processCalledTimes); - for (let i = 1; i <= processCalledTimes; i++) { - expect(unmarshall).toHaveBeenNthCalledWith(i, attrValue(i), undefined); - } + expect(unmarshallOutput(attrObj, keyNodes, { convertWithoutMapWrapper: true })).toEqual(nativeAttrObj); }); }); }); diff --git a/lib/lib-dynamodb/src/commands/utils.ts b/lib/lib-dynamodb/src/commands/utils.ts index 6a621a167d99..2765aa5dc959 100644 --- a/lib/lib-dynamodb/src/commands/utils.ts +++ b/lib/lib-dynamodb/src/commands/utils.ts @@ -1,61 +1,104 @@ -// smithy-typescript generated code import { marshall, marshallOptions, unmarshall, unmarshallOptions } from "@aws-sdk/util-dynamodb"; -export type KeyNode = { - key: string; - children?: KeyNode[] | AllNodes; -}; +/** + * @internal + */ +export type KeyNodeSelf = null; +/** + * @internal + */ +export const SELF: KeyNodeSelf = null; -export type AllNodes = { - children?: KeyNode[] | AllNodes; -}; +/** + * @internal + */ +export type KeyNodeChildren = Record; +/** + * @internal + */ +export const ALL_VALUES: KeyNodeChildren = {}; +/** + * @internal + */ +export const ALL_MEMBERS: KeyNodeChildren = []; +/** + * @internal + */ +const NEXT_LEVEL = "*"; -const processObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => { +/** + * @internal + */ +export type KeyNodes = KeyNodeSelf | KeyNodeChildren; + +const processObj = (obj: any, processFunc: Function, keyNodes?: KeyNodes): any => { if (obj !== undefined) { - if (!children || (Array.isArray(children) && children.length === 0)) { + if (keyNodes == null) { // Leaf of KeyNode, process the object. return processFunc(obj); } else { + const keys = Object.keys(keyNodes); + + const goToNextLevel = keys.length === 1 && keys[0] === NEXT_LEVEL; + const someChildren = keys.length >= 1 && !goToNextLevel; + const allChildren = keys.length === 0; + // Not leaf node, process the children. - if (Array.isArray(children)) { - // Specific keys of children need to be processed. - return processKeysInObj(obj, processFunc, children); - } else { - // All children require processing. - return processAllKeysInObj(obj, processFunc, children.children); + if (someChildren) { + return processKeysInObj(obj, processFunc, keyNodes as KeyNodeChildren); + } else if (allChildren) { + return processAllKeysInObj(obj, processFunc, SELF); + } else if (goToNextLevel) { + return Object.entries(obj ?? {}).reduce((acc, [k, v]) => { + acc[k] = processObj(v, processFunc, keyNodes[NEXT_LEVEL]); + return acc; + }, (Array.isArray(obj) ? [] : {}) as any); } } } return undefined; }; -const processKeyInObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => { +const processKeysInObj = (obj: any, processFunc: Function, keyNodes: KeyNodeChildren) => { + let accumulator: any; if (Array.isArray(obj)) { - return obj.map((item: any) => processObj(item, processFunc, children)); + accumulator = [...obj]; + } else { + accumulator = { ...obj }; } - return processObj(obj, processFunc, children); -}; -const processKeysInObj = (obj: any, processFunc: Function, keyNodes: KeyNode[]) => { - const accumulator = { ...obj }; - return keyNodes.reduce((acc, { key, children }) => { - acc[key] = processKeyInObj(acc[key], processFunc, children); - return acc; - }, accumulator); + for (const [nodeKey, nodes] of Object.entries(keyNodes)) { + const processedValue = processObj(obj[nodeKey], processFunc, nodes); + if (processedValue !== undefined) { + accumulator[nodeKey] = processedValue; + } + } + + return accumulator; }; -const processAllKeysInObj = (obj: any, processFunc: Function, children?: KeyNode[] | AllNodes): any => - Object.entries(obj).reduce((acc, [key, value]) => { - acc[key] = processKeyInObj(value, processFunc, children); +const processAllKeysInObj = (obj: any, processFunc: Function, keyNodes: KeyNodes): any => { + if (Array.isArray(obj)) { + return obj.map((item) => processObj(item, processFunc, keyNodes)); + } + return Object.entries(obj).reduce((acc, [key, value]) => { + acc[key] = processObj(value, processFunc, keyNodes); return acc; }, {} as any); +}; -export const marshallInput = (obj: any, keyNodes: KeyNode[], options?: marshallOptions) => { +/** + * @internal + */ +export const marshallInput = (obj: any, keyNodes: KeyNodeChildren, options?: marshallOptions) => { const marshallFunc = (toMarshall: any) => marshall(toMarshall, options); return processKeysInObj(obj, marshallFunc, keyNodes); }; -export const unmarshallOutput = (obj: any, keyNodes: KeyNode[], options?: unmarshallOptions) => { +/** + * @internal + */ +export const unmarshallOutput = (obj: any, keyNodes: KeyNodeChildren, options?: unmarshallOptions) => { const unmarshallFunc = (toMarshall: any) => unmarshall(toMarshall, options); return processKeysInObj(obj, unmarshallFunc, keyNodes); }; diff --git a/lib/lib-dynamodb/src/test/lib-dynamodb.e2e.spec.ts b/lib/lib-dynamodb/src/test/lib-dynamodb.e2e.spec.ts new file mode 100644 index 000000000000..0bb80af2fed6 --- /dev/null +++ b/lib/lib-dynamodb/src/test/lib-dynamodb.e2e.spec.ts @@ -0,0 +1,570 @@ +import { + BillingMode, + CreateTableCommandOutput, + DeleteItemCommandOutput, + DescribeTableCommandOutput, + DynamoDB, + GetItemCommandOutput, + waitUntilTableExists, +} from "@aws-sdk/client-dynamodb"; +import { + BatchExecuteStatementCommandOutput, + BatchGetCommandOutput, + BatchWriteCommandOutput, + DynamoDBDocument, + ExecuteStatementCommandOutput, + ExecuteTransactionCommandOutput, + GetCommandOutput, + PutCommandOutput, + QueryCommandOutput, + ScanCommandOutput, + TransactGetCommandOutput, + TransactWriteCommandOutput, + UpdateCommandOutput, +} from "@aws-sdk/lib-dynamodb"; + +jest.setTimeout(60000); // expected running time: 10s + +describe(DynamoDBDocument.name, () => { + const dynamodb = new DynamoDB({ region: "us-west-2", maxAttempts: 10 }); + const doc = DynamoDBDocument.from(dynamodb, { + marshallOptions: { + convertTopLevelContainer: true, + }, + }); + + function throwIfError(e: unknown) { + if (e instanceof Error) { + throw e; + } + } + + // Random element limited to 0-99 to avoid concurrent build IO. + // Tables will be dropped at the end of the test. + // For faster test development, remove this random suffix and + // don't delete the table in afterAll(). + // The table will in that case be re-used. + const randId = String((Math.random() * 99) | 0); + + const TableName = `js-sdk-dynamodb-test-${randId}`; + + const log = { + describe: null as null | DescribeTableCommandOutput, + create: null as null | CreateTableCommandOutput, + write: {} as Record, + read: {} as Record, + batchWrite: null as null | BatchWriteCommandOutput, + batchRead: null as null | BatchGetCommandOutput, + transactWrite: null as null | TransactWriteCommandOutput, + transactRead: null as null | TransactGetCommandOutput, + executeTransaction: null as null | ExecuteTransactionCommandOutput, + executeTransactionReadBack: {} as Record, + executeStatement: {} as Record, + executeStatementReadBack: {} as Record, + batchExecuteStatement: null as null | BatchExecuteStatementCommandOutput, + batchExecuteStatementReadBack: null as null | BatchExecuteStatementCommandOutput, + query: null as null | QueryCommandOutput, + scan: null as null | ScanCommandOutput, + update: {} as Record, + updateReadBack: {} as Record, + delete: {} as Record, + }; + + const data = { + null: null, + string: "myString", + number: 1, + boolean: true, + sSet: new Set(["my", "string", "set"]), + nSet: new Set([2, 3, 4]), + list: [ + null, + "myString", + 1, + true, + new Set(["my", "string", "set"]), + new Set([2, 3, 4]), + ["listInList", 1, null], + { + mapInList: "mapInList", + }, + ], + map: { + null: null, + string: "myString", + number: 1, + boolean: true, + sSet: new Set(["my", "string", "set"]), + nSet: new Set([2, 3, 4]), + listInMap: ["listInMap", 1, null], + mapInMap: { mapInMap: "mapInMap" }, + }, + }; + + const updateTransform = (input: T): T => { + switch (typeof input) { + case "object": + if (input === null) { + return null as T; + } + if (Array.isArray(input)) { + return input.map(updateTransform) as T; + } + if (input instanceof Set) { + return new Set([...input].map(updateTransform)) as T; + } + return Object.entries(input).reduce((acc, [k, v]) => { + acc[updateTransform(k)] = updateTransform(v); + return acc; + }, {}) as T; + case "boolean": + return !input as T; + case "number": + return (input + 1) as T; + case "string": + return (input + "-x") as T; + } + return input; + }; + + const passError = (e) => e; + + beforeAll(async () => { + log.describe = await dynamodb + .describeTable({ + TableName, + }) + .catch((e) => { + return null; + }); + if (!log.describe?.Table) { + log.create = await dynamodb + .createTable({ + TableName, + AttributeDefinitions: [ + { + AttributeName: "id", + AttributeType: "S", + }, + ], + KeySchema: [ + { + AttributeName: "id", + KeyType: "HASH", + }, + ], + BillingMode: BillingMode.PAY_PER_REQUEST, + }) + .catch(passError); + await waitUntilTableExists( + { client: dynamodb, maxWaitTime: 60 }, + { + TableName, + } + ); + } + + for (const [id, value] of [["1", data as any], ...Object.entries(data)]) { + log.write[id] = await doc + .put({ + TableName, + Item: { + id, + data: value, + }, + }) + .catch(passError); + + log.read[id] = await doc + .get({ + TableName, + Key: { + id, + }, + }) + .catch(passError); + } + + log.batchWrite = await doc + .batchWrite({ + RequestItems: { + [TableName]: [ + ...Object.entries(data).map(([k, v]) => { + return { + PutRequest: { + Item: { + id: k + "-batch", + data: v, + }, + }, + }; + }), + ], + }, + }) + .catch(passError); + + log.batchRead = await doc + .batchGet({ + RequestItems: { + [TableName]: { + Keys: [ + ...Object.keys(data).map((key) => { + return { id: key + "-batch" }; + }), + ], + }, + }, + }) + .catch(passError); + + log.transactWrite = await doc + .transactWrite({ + TransactItems: [ + ...Object.entries(data).map(([k, v]) => { + return { + Put: { + TableName, + Key: { + id: k + "-transact", + }, + Item: { + id: k + "-transact", + data: v, + }, + }, + }; + }), + ], + }) + .catch(passError); + + log.transactRead = await doc + .transactGet({ + TransactItems: [ + ...Object.keys(data).map((k) => { + return { + Get: { + TableName, + Key: { + id: k + "-transact", + }, + }, + }; + }), + ], + }) + .catch(passError); + + log.executeTransaction = await doc + .executeTransaction({ + TransactStatements: [ + ...Object.entries(data).map(([k, v]) => { + return { + Statement: `INSERT INTO "${TableName}" value {'id':?,'data':?}`, + Parameters: [k + "-exec-transact", v], + }; + }), + ], + }) + .catch(passError); + for (const [k] of Object.entries(data)) { + log.executeTransactionReadBack[k] = await doc + .get({ + TableName, + Key: { + id: k + "-exec-transact", + }, + }) + .catch(passError); + } + + for (const [k, v] of Object.entries(data)) { + log.executeStatement[k] = await doc + .executeStatement({ + Statement: `INSERT INTO "${TableName}" value {'id':?,'data':?}`, + Parameters: [k + "-statement", v], + }) + .catch(passError); + } + for (const [k] of Object.entries(data)) { + log.executeStatementReadBack[k] = await doc + .get({ + TableName, + Key: { + id: k + "-statement", + }, + }) + .catch(passError); + } + + log.batchExecuteStatement = await doc + .batchExecuteStatement({ + Statements: [ + ...Object.entries(data).map(([k, v]) => { + return { + Statement: `INSERT INTO "${TableName}" value {'id':?,'data':?}`, + Parameters: [k + "-batch-statement", v], + }; + }), + ], + }) + .catch(passError); + + log.batchExecuteStatementReadBack = await doc + .batchExecuteStatement({ + Statements: [ + ...Object.entries(data).map(([k, v]) => { + return { + Statement: `SELECT * FROM ${TableName} WHERE "id" = ?`, + Parameters: [k + "-batch-statement"], + }; + }), + ], + }) + .catch(passError); + + log.query = await doc + .query({ + TableName, + KeyConditionExpression: `id = :id`, + ExpressionAttributeValues: { + ":id": "map", + }, + ConsistentRead: true, + }) + .catch(passError); + + log.scan = await doc + .scan({ + TableName, + FilterExpression: `#data = :data1 OR #data = :data2`, + ExpressionAttributeNames: { + "#data": "data", + }, + ExpressionAttributeValues: { + ":data1": data.list, + ":data2": data.map, + }, + }) + .catch(passError); + + for (const [id, value] of [["1", data as any], ...Object.entries(data)]) { + log.update[id] = await doc + .update({ + TableName, + Key: { + id, + }, + AttributeUpdates: { + data: { + Action: "PUT", + Value: updateTransform(value), + }, + }, + }) + .catch(passError); + + log.updateReadBack[id] = await doc + .get({ + TableName, + Key: { + id, + }, + }) + .catch(passError); + + log.delete[id] = await (async () => { + for (const suffix of ["-batch", "-transact", "-exec-transact", "-statement", "-batch-statement"]) { + doc + .delete({ + TableName, + Key: { + id: id + suffix, + }, + }) + .catch(() => {}); + } + return doc.delete({ + TableName, + Key: { id }, + }); + })().catch(passError); + } + }); + + afterAll(async () => { + await dynamodb.deleteTable({ + TableName, + }); + }); + + describe("updateTransformFunction", () => { + it("modifies all fields of an object", () => { + expect(updateTransform(data)).toEqual({ + "null-x": null, + "string-x": "myString-x", + "number-x": 2, + "boolean-x": false, + "sSet-x": new Set(["my-x", "string-x", "set-x"]), + "nSet-x": new Set([3, 4, 5]), + "list-x": [ + null, + "myString-x", + 2, + false, + new Set(["my-x", "string-x", "set-x"]), + new Set([3, 4, 5]), + ["listInList-x", 2, null], + { "mapInList-x": "mapInList-x" }, + ], + "map-x": { + "null-x": null, + "string-x": "myString-x", + "number-x": 2, + "boolean-x": false, + "sSet-x": new Set(["my-x", "string-x", "set-x"]), + "nSet-x": new Set([3, 4, 5]), + "listInMap-x": ["listInMap-x", 2, null], + "mapInMap-x": { "mapInMap-x": "mapInMap-x" }, + }, + }); + }); + }); + + it("initializes using the static constructor", async () => { + expect(doc).toBeInstanceOf(DynamoDBDocument); + }); + + it(`is using a random TableName=${TableName}`, async () => { + // to report the table name + }); + + it("describes the test table tables", async () => { + if (log.describe) { + expect(log.describe?.Table?.TableName).toEqual(TableName); + } + }); + + it("creates the test table if it does not exist", async () => { + if (log.describe) { + throwIfError(log.describe); + expect(log.describe?.Table?.TableName).toEqual(TableName); + } else { + throwIfError(log.create); + expect(log.create?.TableDescription?.TableName).toEqual(TableName); + } + }); + + it("can batch write", async () => { + throwIfError(log.batchWrite); + }); + + it("can batch read", async () => { + throwIfError(log.batchRead); + const results = log.batchRead?.Responses?.[TableName] ?? []; + + for (const result of results) { + expect(result.data).toEqual(data[result.id.replace("-batch", "")]); + } + }); + + it("can transact write", async () => { + throwIfError(log.transactWrite); + }); + + it("can transact read", async () => { + throwIfError(log.transactRead); + const results = log.transactRead?.Responses ?? []; + + for (const result of results) { + expect(result.Item?.data).toEqual(data[result.Item?.id.replace("-transact", "")]); + } + }); + + it("can execute transactions", async () => { + throwIfError(log.executeTransaction); + }); + + it("can batch execute statements", async () => { + throwIfError(log.batchExecuteStatement); + + expect(log.batchExecuteStatementReadBack?.Responses).toBeInstanceOf(Array); + expect(log.batchExecuteStatementReadBack?.Responses?.length).toBeGreaterThan(0); + for (const response of log.batchExecuteStatementReadBack?.Responses ?? []) { + expect(response.Item?.data).toEqual(data[response.Item?.id?.replace("-batch-statement", "")]); + } + }); + + it("can query", async () => { + throwIfError(log.query); + expect(log.query?.Items).toContainEqual({ + id: "map", + data: data.map, + }); + }); + + it("can scan", async () => { + throwIfError(log.scan); + expect(log.scan?.Items).toContainEqual({ + id: "map", + data: data.map, + }); + + expect(log.scan?.Items).toContainEqual({ + id: "list", + data: data.list, + }); + }); + + for (const [key, value] of Object.entries(data)) { + it(`can write data of type ${key}`, async () => { + throwIfError(log.write[key]); + expect(log.write[key].$metadata).toBeDefined(); + }); + + it(`can execute statement inserting type ${key}`, async () => { + const match = log.executeStatement[key]; + expect(match).toBeDefined(); + + throwIfError(match); + }); + + it(`can read back data inserted via ExecuteStatement of type ${key}`, async () => { + throwIfError(log.executeStatementReadBack[key]); + expect(log.executeStatementReadBack[key].Item).toEqual({ + id: key + "-statement", + data: value, + }); + }); + + it(`can read back data inserted via ExecuteTransaction of type ${key}`, async () => { + throwIfError(log.executeTransactionReadBack[key]); + expect(log.executeTransactionReadBack[key].Item).toEqual({ + id: key + "-exec-transact", + data: value, + }); + }); + + it(`can read data of type ${key}`, async () => { + throwIfError(log.read[key]); + expect(log.read[key].Item).toEqual({ + id: key, + data: value, + }); + }); + + it(`can update data of type ${key}`, async () => { + throwIfError(log.updateReadBack[key]); + expect(log.updateReadBack[key].Item).toEqual({ + id: key, + data: updateTransform(value), + }); + }); + + it(`can delete data of type ${key}`, async () => { + throwIfError(log.delete[key]); + expect(log.delete[key].$metadata).toBeDefined(); + }); + } +}); diff --git a/packages/util-dynamodb/src/convertToAttr.ts b/packages/util-dynamodb/src/convertToAttr.ts index 27c492491949..d73527401b0e 100644 --- a/packages/util-dynamodb/src/convertToAttr.ts +++ b/packages/util-dynamodb/src/convertToAttr.ts @@ -4,10 +4,10 @@ import { marshallOptions } from "./marshall"; import { NativeAttributeBinary, NativeAttributeValue, NativeScalarAttributeValue } from "./models"; /** - * Convert a JavaScript value to its equivalent DynamoDB AttributeValue type + * Convert a JavaScript value to its equivalent DynamoDB AttributeValue type. * - * @param {NativeAttributeValue} data - The data to convert to a DynamoDB AttributeValue - * @param {marshallOptions} options - An optional configuration object for `convertToAttr` + * @param data - The data to convert to a DynamoDB AttributeValue. + * @param options - An optional configuration object for `convertToAttr`. */ export const convertToAttr = (data: NativeAttributeValue, options?: marshallOptions): AttributeValue => { if (data === undefined) { diff --git a/packages/util-dynamodb/src/convertToNative.ts b/packages/util-dynamodb/src/convertToNative.ts index a22c525d2b80..89ab5777fe6a 100644 --- a/packages/util-dynamodb/src/convertToNative.ts +++ b/packages/util-dynamodb/src/convertToNative.ts @@ -6,8 +6,8 @@ import { unmarshallOptions } from "./unmarshall"; /** * Convert a DynamoDB AttributeValue object to its equivalent JavaScript type. * - * @param {AttributeValue} data - The DynamoDB record to convert to JavaScript type. - * @param {unmarshallOptions} options - An optional configuration object for `convertToNative`. + * @param data - The DynamoDB record to convert to JavaScript type. + * @param options - An optional configuration object for `convertToNative`. */ export const convertToNative = (data: AttributeValue, options?: unmarshallOptions): NativeAttributeValue => { for (const [key, value] of Object.entries(data)) { diff --git a/packages/util-dynamodb/src/marshall.ts b/packages/util-dynamodb/src/marshall.ts index 8139f583a07f..717838d564d2 100644 --- a/packages/util-dynamodb/src/marshall.ts +++ b/packages/util-dynamodb/src/marshall.ts @@ -19,13 +19,21 @@ export interface marshallOptions { * Whether to convert typeof object to map attribute. */ convertClassInstanceToMap?: boolean; + /** + * Whether to convert the top level container + * if it is a map or list. + * + * Default is true when using the DynamoDBDocumentClient, + * but false if directly using the marshall function (backwards compatibility). + */ + convertTopLevelContainer?: boolean; } /** * Convert a JavaScript object into a DynamoDB record. * - * @param {any} data - The data to convert to a DynamoDB record - * @param {marshallOptions} options - An optional configuration object for `marshall` + * @param data - The data to convert to a DynamoDB record + * @param options - An optional configuration object for `marshall` * */ export function marshall(data: Set, options?: marshallOptions): AttributeValue.SSMember; @@ -49,7 +57,7 @@ export function marshall(data: unknown, options?: marshallOptions) { switch (key) { case "M": case "L": - return value; + return options?.convertTopLevelContainer ? attributeValue : value; case "SS": case "NS": case "BS": diff --git a/packages/util-dynamodb/src/unmarshall.ts b/packages/util-dynamodb/src/unmarshall.ts index 3ef8e93befe3..c705e93ea70f 100644 --- a/packages/util-dynamodb/src/unmarshall.ts +++ b/packages/util-dynamodb/src/unmarshall.ts @@ -12,16 +12,30 @@ export interface unmarshallOptions { * This allows for the safe round-trip transport of numbers of arbitrary size. */ wrapNumbers?: boolean; + /** + * When true, skip wrapping the data in `{ M: data }` before converting. + * + * Default is true when using the DynamoDBDocumentClient, + * but false if directly using the unmarshall function (backwards compatibility). + */ + convertWithoutMapWrapper?: boolean; } /** * Convert a DynamoDB record into a JavaScript object. * - * @param {any} data - The DynamoDB record - * @param {unmarshallOptions} options - An optional configuration object for `unmarshall` + * @param data - The DynamoDB record + * @param options - An optional configuration object for `unmarshall` */ export const unmarshall = ( - data: Record, + data: Record | AttributeValue, options?: unmarshallOptions -): Record => - convertToNative({ M: data }, options) as Record; +): Record => { + if (options?.convertWithoutMapWrapper) { + return convertToNative(data as AttributeValue, options); + } + return convertToNative({ M: data as Record }, options) as Record< + string, + NativeAttributeValue + >; +}; diff --git a/scripts/generate-clients/single-service.js b/scripts/generate-clients/single-service.js index bba72ec51a42..b8f0dd24a796 100644 --- a/scripts/generate-clients/single-service.js +++ b/scripts/generate-clients/single-service.js @@ -24,6 +24,7 @@ const { solo } = yargs(process.argv.slice(2)) // post-generation transforms const clientFolder = join(SDK_CLIENTS_DIR, `client-${solo}`); + const libFolder = join(SDK_CLIENTS_DIR, "..", "lib", `lib-${solo}`); // examples merging require("../api-examples/get-examples"); @@ -34,6 +35,12 @@ const { solo } = yargs(process.argv.slice(2)) await spawnProcess("npx", ["eslint", "--quiet", "--fix", `${clientFolder}/src/**/*`]); } catch (ignored) {} + if (solo === "dynamodb") { + try { + await spawnProcess("npx", ["eslint", "--quiet", "--fix", `${libFolder}/src/**/*`]); + } catch (ignored) {} + } + console.log("================ starting prettier ================", "\n", new Date().toString(), solo); await spawnProcess("npx", [ "prettier", @@ -43,6 +50,9 @@ const { solo } = yargs(process.argv.slice(2)) `${clientFolder}/src/**/*.{md,js,ts,json}`, ]); await spawnProcess("npx", ["prettier", "--write", "--loglevel", "warn", `${clientFolder}/README.md`]); + if (solo === "dynamodb") { + await spawnProcess("npx", ["prettier", "--write", "--loglevel", "warn", `${libFolder}/src/**/*.{md,js,ts,json}`]); + } const compress = require("../endpoints-ruleset/compress"); compress(solo);