Skip to content

Commit

Permalink
fix(lib-dynamodb): make command middleware useable, turn marshalling …
Browse files Browse the repository at this point in the history
…into middleware
  • Loading branch information
kuhe committed Jul 20, 2022
1 parent b9873cb commit 59910f2
Show file tree
Hide file tree
Showing 17 changed files with 498 additions and 262 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@

package software.amazon.smithy.aws.typescript.codegen;

import static software.amazon.smithy.aws.typescript.codegen.propertyaccess.PropertyAccessor.getFrom;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
Expand Down Expand Up @@ -55,10 +53,14 @@ final class DocumentClientCommandGenerator implements Runnable {
private final TypeScriptWriter writer;
private final Symbol symbol;
private final OperationIndex operationIndex;
private final String originalInputTypeName;
private final String inputTypeName;
private final List<MemberShape> inputMembersWithAttr;
private final String originalOutputTypeName;
private final String outputTypeName;
private final List<MemberShape> outputMembersWithAttr;
private final String clientCommandClassName;
private final String clientCommandLocalName;

DocumentClientCommandGenerator(
TypeScriptSettings settings,
Expand All @@ -75,14 +77,21 @@ final class DocumentClientCommandGenerator implements Runnable {

symbol = symbolProvider.toSymbol(operation);
operationIndex = OperationIndex.of(model);
String inputType = symbol.expectProperty("inputType", Symbol.class).getName();
originalInputTypeName = inputType;
inputTypeName = DocumentClientUtils.getModifiedName(
symbol.expectProperty("inputType", Symbol.class).getName()
inputType
);
inputMembersWithAttr = getStructureMembersWithAttr(operationIndex.getInput(operation));
String outputType = symbol.expectProperty("outputType", Symbol.class).getName();
originalOutputTypeName = outputType;
outputTypeName = DocumentClientUtils.getModifiedName(
symbol.expectProperty("outputType", Symbol.class).getName()
outputType
);
outputMembersWithAttr = getStructureMembersWithAttr(operationIndex.getOutput(operation));

clientCommandClassName = symbol.getName();
clientCommandLocalName = "__" + clientCommandClassName;
}

@Override
Expand All @@ -92,37 +101,57 @@ public void run() {

// Add required imports.
writer.addImport(configType, configType, servicePath);
writer.addImport("Command", "$Command", "@aws-sdk/smithy-client");
writer.addImport(
"DynamoDBDocumentClientCommand",
"DynamoDBDocumentClientCommand",
"./baseCommand/DynamoDBDocumentClientCommand"
);

generateInputAndOutputTypes();

String ioTypes = String.join(", ", new String[]{
inputTypeName,
outputTypeName,
"__" + originalInputTypeName,
"__" + originalOutputTypeName
});

String name = DocumentClientUtils.getModifiedName(symbol.getName());
writer.writeDocs(DocumentClientUtils.getCommandDocs(symbol.getName()));
writer.openBlock("export class $L extends $$Command<$L, $L, $L> {", "}",
name, inputTypeName, outputTypeName, configType, () -> {

// Section for adding custom command properties.
writer.pushState(COMMAND_PROPERTIES_SECTION);
if (!inputMembersWithAttr.isEmpty()) {
writer.openBlock("private readonly $L = [", "];", COMMAND_INPUT_KEYNODES, () -> {
writer.openBlock(
"export class $L extends DynamoDBDocumentClientCommand<" + ioTypes + ", $L> {",
"}",
name,
configType,
() -> {
// Section for adding custom command properties.
writer.pushState(COMMAND_PROPERTIES_SECTION);
writer.openBlock("protected readonly $L = [", "];", COMMAND_INPUT_KEYNODES, () -> {
writeKeyNodes(inputMembersWithAttr);
});
}
if (!outputMembersWithAttr.isEmpty()) {
writer.openBlock("private readonly $L = [", "];", COMMAND_OUTPUT_KEYNODES, () -> {
writer.openBlock("protected readonly $L = [", "];", COMMAND_OUTPUT_KEYNODES, () -> {
writeKeyNodes(outputMembersWithAttr);
});
}
writer.popState();
writer.write("");
writer.popState();
writer.write("");

writer.write("protected readonly clientCommand = new $L(this.input as any);", clientCommandLocalName);
writer.write("protected readonly clientCommandName = $L.name", clientCommandLocalName);
writer.write(
"public readonly middlewareStack: MiddlewareStack<$L> = this.clientCommand.middlewareStack;",
inputTypeName + " | __" + originalInputTypeName
+ ", \n" + outputTypeName + " | __" + originalOutputTypeName
);
writer.write("");

generateCommandConstructor();
writer.write("");
generateCommandMiddlewareResolver(configType);
generateCommandConstructor();
writer.write("");
generateCommandMiddlewareResolver(configType);

// Hook for adding more methods to the command.
writer.pushState(COMMAND_BODY_EXTRA_SECTION).popState();
});
// Hook for adding more methods to the command.
writer.pushState(COMMAND_BODY_EXTRA_SECTION).popState();
}
);
}

private void generateCommandConstructor() {
Expand Down Expand Up @@ -155,52 +184,27 @@ private void generateCommandMiddlewareResolver(String configType) {
.write("options?: $T", ApplicationProtocol.createDefaultHttpApplicationProtocol().getOptionsType())
.dedent();
writer.openBlock("): $L<$L, $L> {", "}", handler, inputTypeName, outputTypeName, () -> {
String marshallOptions = DocumentClientUtils.CLIENT_MARSHALL_OPTIONS;
String unmarshallOptions = DocumentClientUtils.CLIENT_UNMARSHALL_OPTIONS;

writer.write("const { $L, $L } = configuration.$L || {};", marshallOptions, unmarshallOptions,
DocumentClientUtils.CLIENT_TRANSLATE_CONFIG_KEY);

writer.addImport(symbol.getName(), "__" + symbol.getName(), "@aws-sdk/client-dynamodb");

String marshallInput = "marshallInput";
String unmarshallOutput = "unmarshallOutput";
String utilsFileLocation = String.format("./%s/%s",
DocumentClientUtils.CLIENT_COMMANDS_FOLDER, DocumentClientUtils.CLIENT_UTILS_FILE);
writer.addImport(marshallInput, marshallInput, utilsFileLocation);
writer.addImport(unmarshallOutput, unmarshallOutput, utilsFileLocation);

String commandVarName = "command";
writer.openBlock("const $L = new $L(", ");", commandVarName, "__" + symbol.getName(),
() -> {
if (inputMembersWithAttr.isEmpty()) {
writer.write("this.input,");
} else {
writer.openBlock("$L(", ")", marshallInput, () -> {
writer.write("this.input,");
writer.write(getFrom("this", COMMAND_INPUT_KEYNODES) + ",");
writer.write("$L,", marshallOptions);
});
}
});

writer.addImport(clientCommandClassName, clientCommandLocalName, "@aws-sdk/client-dynamodb");

String commandVarName = "this.clientCommand";

// marshall middlewares
writer.openBlock("this.addMarshallingMiddleware(", ");", () -> {
writer.write("configuration");
});

writer.write("const stack = clientStack.concat(this.middlewareStack as typeof clientStack);");

String handlerVarName = "handler";
writer.write("const $L = $L.resolveMiddleware(clientStack, configuration, options);",
writer.write("const $L = $L.resolveMiddleware(stack, configuration, options);",
handlerVarName, commandVarName);
writer.write("");

if (outputMembersWithAttr.isEmpty()) {
writer.write("return $L;", handlerVarName);
} else {
writer.openBlock("return async () => {", "};", () -> {
String dataVarName = "data";
String outputVarName = "output";
writer.write("const $L = await $L($L);", dataVarName, handlerVarName, commandVarName);
writer.openBlock("return {", "};", () -> {
writer.write("...$L,", dataVarName);
writer.write("$1L: $2L($3L.$1L, this.$4L, $5L),", outputVarName, unmarshallOutput,
dataVarName, COMMAND_OUTPUT_KEYNODES, unmarshallOptions);
});
});
writer.write("return async () => handler($L)", commandVarName);
}
});
}
Expand Down Expand Up @@ -259,10 +263,8 @@ private void writeStructureKeyNode(StructureShape structureTarget) {

private void generateInputAndOutputTypes() {
writer.write("");
String originalInputTypeName = symbol.expectProperty("inputType", Symbol.class).getName();
writeType(inputTypeName, originalInputTypeName, operationIndex.getInput(operation), inputMembersWithAttr);
writer.write("");
String originalOutputTypeName = symbol.expectProperty("outputType", Symbol.class).getName();
writeType(outputTypeName, originalOutputTypeName, operationIndex.getOutput(operation), outputMembersWithAttr);
writer.write("");
}
Expand Down
73 changes: 73 additions & 0 deletions lib/lib-dynamodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,79 @@ await ddbDocClient.put({
});
```

### Client and Command middleware stacks

As with other AWS SDK for JavaScript v3 clients, you can apply middleware functions
both on the client itself and individual `Command`s.

For individual `Command`s, here are examples of how to add middleware before and after
both marshalling and unmarshalling. We will use `QueryCommand` as an example.
Others follow the same pattern.

```js
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({
/*...*/
});
const doc = DynamoDBDocumentClient.from(client);
const command = new QueryCommand({
/*...*/
});
```

Before and after marshalling:

```js
command.middlewareStack.addRelativeTo(
(next) => async (args) => {
console.log("pre-marshall", args.input);
return next(args);
},
{
relation: "before",
toMiddleware: "QueryCommandMarshall",
}
);
command.middlewareStack.addRelativeTo(
(next) => async (args) => {
console.log("post-marshall", args.input);
return next(args);
},
{
relation: "after",
toMiddleware: "QueryCommandMarshall",
}
);
```

Before and after unmarshalling:

```js
command.middlewareStack.addRelativeTo(
(next) => async (args) => {
const result = await next(args);
console.log("pre-unmarshall", result.output.Items);
return result;
},
{
relation: "after", // <- after for pre-unmarshall
toMiddleware: "QueryCommandUnmarshall",
}
);
command.middlewareStack.addRelativeTo(
(next) => async (args) => {
const result = await next(args);
console.log("post-unmarshall", result.output.Items);
return result;
},
{
relation: "before", // <- before for post-unmarshall
toMiddleware: "QueryCommandUnmarshall",
}
);
```

### Destroying document client

The `destroy()` call on document client is a no-op as document client does not
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Handler, MiddlewareStack } from "@aws-sdk/types";

import { KeyNode } from "../commands/utils";
import { DynamoDBDocumentClientCommand } from "./DynamoDBDocumentClientCommand";

class AnyCommand extends DynamoDBDocumentClientCommand<{}, {}, {}, {}, {}> {
public middlewareStack: MiddlewareStack<{}, {}>;
public input: {};
protected inputKeyNodes: KeyNode[] = [];
protected outputKeyNodes: KeyNode[] = [];

public argCaptor: [Function, object][] = [];

protected readonly clientCommand = {
middlewareStack: {
argCaptor: this.argCaptor,
add(fn, config) {
this.argCaptor.push([fn, config]);
},
},
} as any;
protected readonly clientCommandName = "AnyCommand";

public constructor() {
super();
}

public resolveMiddleware(clientStack: MiddlewareStack<any, any>, configuration: {}, options: any): Handler<{}, {}> {
this.addMarshallingMiddleware({} as any);
return null as any;
}
}

describe("DynamoDBDocumentClientCommand", () => {
it("should not allow usage of the default middlewareStack", () => {
const command = new AnyCommand();
command.resolveMiddleware(null as any, null as any, null as any);
{
const [middleware, options] = command.argCaptor[0];
expect(middleware.toString()).toContain(`marshallInput`);
expect(options).toEqual({
name: "AnyCommandMarshall",
override: true,
step: "initialize",
});
}
{
const [middleware, options] = command.argCaptor[1];
expect(middleware.toString()).toContain(`unmarshallOutput`);
expect(options).toEqual({
name: "AnyCommandUnmarshall",
override: true,
step: "deserialize",
});
}
});
});

0 comments on commit 59910f2

Please sign in to comment.