Skip to content

Commit a8b0d25

Browse files
committed
feat: add Command ClassBuilder
1 parent f0ff3b7 commit a8b0d25

File tree

3 files changed

+307
-3
lines changed

3 files changed

+307
-3
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: 264 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
import { EndpointParameterInstructions } from "@smithy/middleware-endpoint";
12
import { constructStack } from "@smithy/middleware-stack";
2-
import { Command as ICommand, Handler, MetadataBearer, MiddlewareStack as IMiddlewareStack } from "@smithy/types";
3+
import type { HttpRequest } from "@smithy/protocol-http";
4+
import type {
5+
Command as ICommand,
6+
FinalizeHandlerArguments,
7+
Handler,
8+
HandlerExecutionContext,
9+
HttpRequest as IHttpRequest,
10+
HttpResponse as IHttpResponse,
11+
Logger,
12+
MetadataBearer,
13+
MiddlewareStack as IMiddlewareStack,
14+
Pluggable,
15+
RequestHandler,
16+
SerdeContext,
17+
} from "@smithy/types";
18+
import { SMITHY_CONTEXT_KEY } from "@smithy/types";
319

420
/**
521
* @public
@@ -11,11 +27,256 @@ export abstract class Command<
1127
ClientInput extends object = any,
1228
ClientOutput extends MetadataBearer = any
1329
> implements ICommand<ClientInput, Input, ClientOutput, Output, ResolvedClientConfiguration> {
14-
abstract input: Input;
15-
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+
}
46+
1647
abstract resolveMiddleware(
1748
stack: IMiddlewareStack<ClientInput, ClientOutput>,
1849
configuration: ResolvedClientConfiguration,
1950
options: any
2051
): Handler<Input, Output>;
52+
53+
/**
54+
* @internal
55+
*/
56+
public resolveMiddlewareWithContext(
57+
clientStack: IMiddlewareStack<any, any>,
58+
configuration: { logger: Logger; requestHandler: RequestHandler<any, any, any> },
59+
options: any,
60+
{
61+
middlewareFn,
62+
clientName,
63+
commandName,
64+
inputFilterSensitiveLog,
65+
outputFilterSensitiveLog,
66+
smithyContext,
67+
additionalContext,
68+
CommandCtor,
69+
}: ResolveMiddlewareContextArgs
70+
) {
71+
for (const mw of middlewareFn.bind(this)(CommandCtor, configuration)) {
72+
this.middlewareStack.use(mw);
73+
}
74+
const stack = clientStack.concat(this.middlewareStack);
75+
const { logger } = configuration;
76+
const handlerExecutionContext: HandlerExecutionContext = {
77+
logger,
78+
clientName,
79+
commandName,
80+
inputFilterSensitiveLog,
81+
outputFilterSensitiveLog,
82+
[SMITHY_CONTEXT_KEY]: {
83+
...smithyContext,
84+
},
85+
...additionalContext,
86+
};
87+
const { requestHandler } = configuration;
88+
return stack.resolve(
89+
(request: FinalizeHandlerArguments<any>) => requestHandler.handle(request.request as HttpRequest, options || {}),
90+
handlerExecutionContext
91+
);
92+
}
93+
}
94+
95+
/**
96+
* @internal
97+
*/
98+
type ResolveMiddlewareContextArgs = {
99+
middlewareFn: (CommandCtor: any, config: any) => Pluggable<any, any>[];
100+
clientName: string;
101+
commandName: string;
102+
smithyContext: Record<string, unknown>;
103+
additionalContext: HandlerExecutionContext;
104+
inputFilterSensitiveLog: (_: any) => any;
105+
outputFilterSensitiveLog: (_: any) => any;
106+
CommandCtor: any /* Command constructor */;
107+
};
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>;
21282
}

0 commit comments

Comments
 (0)