Skip to content

Commit d872af6

Browse files
committed
command class builder
1 parent c4d8204 commit d872af6

File tree

6 files changed

+400
-253
lines changed

6 files changed

+400
-253
lines changed

.changeset/cool-vans-tan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/smithy-client": minor
3+
---
4+
5+
add Command classBuilder
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Command } from "./command";
2+
3+
describe(Command.name, () => {
4+
it("implements a classBuilder", async () => {
5+
class MyCommand extends Command.classBuilder<any, any, any, any, any>()
6+
.ep({
7+
Endpoint: { type: "builtInParams", name: "Endpoint" },
8+
})
9+
.m(function () {
10+
return [];
11+
})
12+
.s("SmithyMyClient", "SmithyMyOperation", {})
13+
.n("MyClient", "MyCommand")
14+
.f()
15+
.ser(async (_) => _)
16+
.de(async (_) => _)
17+
.build() {}
18+
19+
const myCommand = new MyCommand({
20+
Prop: "prop1",
21+
});
22+
23+
expect(myCommand).toBeInstanceOf(Command);
24+
expect(myCommand).toBeInstanceOf(MyCommand);
25+
expect(MyCommand.getEndpointParameterInstructions()).toEqual({
26+
Endpoint: { type: "builtInParams", name: "Endpoint" },
27+
});
28+
expect(myCommand.input).toEqual({
29+
Prop: "prop1",
30+
});
31+
32+
// private method exists for compatibility
33+
expect((myCommand as any).serialize).toBeDefined();
34+
35+
// private method exists for compatibility
36+
expect((myCommand as any).deserialize).toBeDefined();
37+
});
38+
});

packages/smithy-client/src/command.ts

Lines changed: 208 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1+
import { EndpointParameterInstructions } from "@smithy/middleware-endpoint";
12
import { constructStack } from "@smithy/middleware-stack";
2-
import type { HttpRequest as __HttpRequest } from "@smithy/protocol-http";
3+
import type { HttpRequest } from "@smithy/protocol-http";
34
import type {
45
Command as ICommand,
56
FinalizeHandlerArguments,
67
Handler,
78
HandlerExecutionContext,
9+
HttpRequest as IHttpRequest,
10+
HttpResponse as IHttpResponse,
811
Logger,
912
MetadataBearer,
1013
MiddlewareStack as IMiddlewareStack,
1114
Pluggable,
1215
RequestHandler,
16+
SerdeContext,
1317
} from "@smithy/types";
1418
import { SMITHY_CONTEXT_KEY } from "@smithy/types";
1519

@@ -23,8 +27,22 @@ export abstract class Command<
2327
ClientInput extends object = any,
2428
ClientOutput extends MetadataBearer = any
2529
> implements ICommand<ClientInput, Input, ClientOutput, Output, ResolvedClientConfiguration> {
26-
abstract input: Input;
27-
readonly middlewareStack: IMiddlewareStack<Input, Output> = constructStack<Input, Output>();
30+
public abstract input: Input;
31+
public readonly middlewareStack: IMiddlewareStack<Input, Output> = constructStack<Input, Output>();
32+
33+
/**
34+
* Factory for Command ClassBuilder.
35+
* @internal
36+
*/
37+
public static classBuilder<
38+
I extends SI,
39+
O extends SO,
40+
C extends { logger: Logger; requestHandler: RequestHandler<any, any, any> },
41+
SI extends object = any,
42+
SO extends MetadataBearer = any
43+
>() {
44+
return new ClassBuilder<I, O, C, SI, SO>();
45+
}
2846

2947
abstract resolveMiddleware(
3048
stack: IMiddlewareStack<ClientInput, ClientOutput>,
@@ -35,81 +53,22 @@ export abstract class Command<
3553
/**
3654
* @internal
3755
*/
38-
protected resolveBuilder() {
39-
const args: ResolveMiddlewareContextArgs = {
40-
middlewareQueue: [] as Pluggable<any, any>[],
41-
commandName: "",
42-
clientName: "",
43-
smithyContext: {},
44-
inputFilterSensitiveLog: () => {},
45-
outputFilterSensitiveLog: () => {},
46-
};
47-
return {
48-
/**
49-
* Add any number of middleware.
50-
*/
51-
m(...middleware: Pluggable<any, any>[]) {
52-
args.middlewareQueue.push(...middleware);
53-
return this;
54-
},
55-
/**
56-
* Set the context record.
57-
*/
58-
c(smithyContext: Record<string, unknown>) {
59-
args.smithyContext = smithyContext;
60-
return this;
61-
},
62-
/**
63-
* Set constant string identifiers for the operation.
64-
*/
65-
n(clientName: string, commandName: string) {
66-
args.clientName = clientName;
67-
args.commandName = commandName;
68-
return this;
69-
},
70-
/**
71-
* Set the input and output sensistive log filters.
72-
*/
73-
f(inputFilter: (_: any) => any = (_) => _, outputFilter: (_: any) => any = (_) => _) {
74-
args.inputFilterSensitiveLog = inputFilter;
75-
args.outputFilterSensitiveLog = outputFilter;
76-
return this;
77-
},
78-
/**
79-
* @returns the implementation of the built resolveMiddleware function.
80-
*/
81-
build: () => {
82-
return (
83-
clientStack: IMiddlewareStack<ClientInput, ClientOutput>,
84-
configuration: ResolvedClientConfiguration & {
85-
logger: Logger;
86-
requestHandler: RequestHandler<any, any, any>;
87-
},
88-
options: any
89-
) => {
90-
return this.__resolveMiddleware(clientStack, configuration, options, args);
91-
};
92-
},
93-
};
94-
}
95-
96-
/**
97-
* @internal
98-
*/
99-
protected __resolveMiddleware(
100-
clientStack: IMiddlewareStack<ClientInput, ClientOutput>,
101-
configuration: ResolvedClientConfiguration & { logger: Logger; requestHandler: RequestHandler<any, any, any> },
56+
public resolveMiddlewareWithContext(
57+
clientStack: IMiddlewareStack<any, any>,
58+
configuration: { logger: Logger; requestHandler: RequestHandler<any, any, any> },
10259
options: any,
10360
{
104-
middlewareQueue,
61+
middlewareFn,
10562
clientName,
10663
commandName,
10764
inputFilterSensitiveLog,
10865
outputFilterSensitiveLog,
10966
smithyContext,
67+
additionalContext,
68+
CommandCtor,
11069
}: ResolveMiddlewareContextArgs
11170
) {
112-
for (const mw of middlewareQueue) {
71+
for (const mw of middlewareFn.bind(this)(CommandCtor, configuration)) {
11372
this.middlewareStack.use(mw);
11473
}
11574
const stack = clientStack.concat(this.middlewareStack);
@@ -123,11 +82,11 @@ export abstract class Command<
12382
[SMITHY_CONTEXT_KEY]: {
12483
...smithyContext,
12584
},
85+
...additionalContext,
12686
};
12787
const { requestHandler } = configuration;
12888
return stack.resolve(
129-
(request: FinalizeHandlerArguments<any>) =>
130-
requestHandler.handle(request.request as __HttpRequest, options || {}),
89+
(request: FinalizeHandlerArguments<any>) => requestHandler.handle(request.request as HttpRequest, options || {}),
13190
handlerExecutionContext
13291
);
13392
}
@@ -137,10 +96,187 @@ export abstract class Command<
13796
* @internal
13897
*/
13998
type ResolveMiddlewareContextArgs = {
140-
middlewareQueue: Pluggable<any, any>[];
99+
middlewareFn: (CommandCtor: any, config: any) => Pluggable<any, any>[];
141100
clientName: string;
142101
commandName: string;
143102
smithyContext: Record<string, unknown>;
103+
additionalContext: HandlerExecutionContext;
144104
inputFilterSensitiveLog: (_: any) => any;
145105
outputFilterSensitiveLog: (_: any) => any;
106+
CommandCtor: any /* Command constructor */;
146107
};
108+
109+
/**
110+
* @internal
111+
*/
112+
class ClassBuilder<
113+
I extends SI,
114+
O extends SO,
115+
C extends { logger: Logger; requestHandler: RequestHandler<any, any, any> },
116+
SI extends object = any,
117+
SO extends MetadataBearer = any
118+
> {
119+
private _init: (_: Command<I, O, C, SI, SO>) => void = () => {};
120+
private _ep: EndpointParameterInstructions = {};
121+
private _middlewareFn: (CommandCtor: any, config: any) => Pluggable<any, any>[] = () => [];
122+
private _commandName = "";
123+
private _clientName = "";
124+
private _additionalContext = {} as HandlerExecutionContext;
125+
private _smithyContext = {} as Record<string, unknown>;
126+
private _inputFilterSensitiveLog = (_: any) => _;
127+
private _outputFilterSensitiveLog = (_: any) => _;
128+
private _serializer: (input: I, context: SerdeContext | any) => Promise<IHttpRequest> = null as any;
129+
private _deserializer: (output: IHttpResponse, context: SerdeContext | any) => Promise<O> = null as any;
130+
/**
131+
* Optional init callback.
132+
*/
133+
public init(cb: (_: Command<I, O, C, SI, SO>) => void) {
134+
this._init = cb;
135+
}
136+
/**
137+
* Set the endpoint parameter instructions.
138+
*/
139+
public ep(endpointParameterInstructions: EndpointParameterInstructions): ClassBuilder<I, O, C, SI, SO> {
140+
this._ep = endpointParameterInstructions;
141+
return this;
142+
}
143+
/**
144+
* Add any number of middleware.
145+
*/
146+
public m(
147+
middlewareSupplier: (CommandCtor: any, config: any) => Pluggable<any, any>[]
148+
): ClassBuilder<I, O, C, SI, SO> {
149+
this._middlewareFn = middlewareSupplier;
150+
return this;
151+
}
152+
/**
153+
* Set the initial handler execution context Smithy field.
154+
*/
155+
public s(
156+
service: string,
157+
operation: string,
158+
smithyContext: Record<string, unknown> = {}
159+
): ClassBuilder<I, O, C, SI, SO> {
160+
this._smithyContext = {
161+
service,
162+
operation,
163+
...smithyContext,
164+
};
165+
return this;
166+
}
167+
/**
168+
* Set the initial handler execution context.
169+
*/
170+
public c(additionalContext: HandlerExecutionContext = {}): ClassBuilder<I, O, C, SI, SO> {
171+
this._additionalContext = additionalContext;
172+
return this;
173+
}
174+
/**
175+
* Set constant string identifiers for the operation.
176+
*/
177+
public n(clientName: string, commandName: string): ClassBuilder<I, O, C, SI, SO> {
178+
this._clientName = clientName;
179+
this._commandName = commandName;
180+
return this;
181+
}
182+
/**
183+
* Set the input and output sensistive log filters.
184+
*/
185+
public f(
186+
inputFilter: (_: any) => any = (_) => _,
187+
outputFilter: (_: any) => any = (_) => _
188+
): ClassBuilder<I, O, C, SI, SO> {
189+
this._inputFilterSensitiveLog = inputFilter;
190+
this._outputFilterSensitiveLog = outputFilter;
191+
return this;
192+
}
193+
/**
194+
* Sets the serializer.
195+
*/
196+
public ser(
197+
serializer: (input: I, context?: SerdeContext | any) => Promise<IHttpRequest>
198+
): ClassBuilder<I, O, C, SI, SO> {
199+
this._serializer = serializer;
200+
return this;
201+
}
202+
/**
203+
* Sets the deserializer.
204+
*/
205+
public de(
206+
deserializer: (output: IHttpResponse, context?: SerdeContext | any) => Promise<O>
207+
): ClassBuilder<I, O, C, SI, SO> {
208+
this._deserializer = deserializer;
209+
return this;
210+
}
211+
/**
212+
* @returns a Command class with the classBuilder properties.
213+
*/
214+
public build(): {
215+
new (input: I): CommandImpl<I, O, C, SI, SO>;
216+
getEndpointParameterInstructions(): EndpointParameterInstructions;
217+
} {
218+
// eslint-disable-next-line @typescript-eslint/no-this-alias
219+
const closure = this;
220+
let CommandRef: any;
221+
222+
return (CommandRef = class extends Command<I, O, C, SI, SO> {
223+
/**
224+
* @public
225+
*/
226+
public static getEndpointParameterInstructions(): EndpointParameterInstructions {
227+
return closure._ep;
228+
}
229+
230+
/**
231+
* @public
232+
*/
233+
public constructor(readonly input: I) {
234+
super();
235+
closure._init(this);
236+
}
237+
238+
/**
239+
* @internal
240+
*/
241+
public resolveMiddleware(stack: IMiddlewareStack<any, any>, configuration: C, options: any): Handler<any, any> {
242+
return this.resolveMiddlewareWithContext(stack, configuration, options, {
243+
CommandCtor: CommandRef,
244+
middlewareFn: closure._middlewareFn,
245+
clientName: closure._clientName,
246+
commandName: closure._commandName,
247+
inputFilterSensitiveLog: closure._inputFilterSensitiveLog,
248+
outputFilterSensitiveLog: closure._outputFilterSensitiveLog,
249+
smithyContext: closure._smithyContext,
250+
additionalContext: closure._additionalContext,
251+
});
252+
}
253+
254+
/**
255+
* @internal
256+
*/
257+
// @ts-ignore used in middlewareFn closure.
258+
public serialize = closure._serializer;
259+
260+
/**
261+
* @internal
262+
*/
263+
// @ts-ignore used in middlewareFn closure.
264+
public deserialize = closure._deserializer;
265+
});
266+
}
267+
}
268+
269+
/**
270+
* A concrete implementation of ICommand with no abstract members.
271+
* @public
272+
*/
273+
export interface CommandImpl<
274+
I extends SI,
275+
O extends SO,
276+
C extends { logger: Logger; requestHandler: RequestHandler<any, any, any> },
277+
SI extends object = any,
278+
SO extends MetadataBearer = any
279+
> extends Command<I, O, C, SI, SO> {
280+
readonly input: I;
281+
resolveMiddleware(stack: IMiddlewareStack<SI, SO>, configuration: C, options: any): Handler<I, O>;
282+
}

0 commit comments

Comments
 (0)