diff --git a/packages/middleware-stack/src/MiddlewareStack.spec.ts b/packages/middleware-stack/src/MiddlewareStack.spec.ts index b6f9397e5611..3e94a1d93010 100644 --- a/packages/middleware-stack/src/MiddlewareStack.spec.ts +++ b/packages/middleware-stack/src/MiddlewareStack.spec.ts @@ -11,7 +11,7 @@ import { Pluggable, } from "@aws-sdk/types"; -import { MiddlewareStack } from "./MiddlewareStack"; +import { constructStack } from "./MiddlewareStack"; type input = Array; type output = object; @@ -29,7 +29,7 @@ function getConcatMiddleware(message: string): MiddlewareType { describe("MiddlewareStack", () => { it("should resolve the stack into a composed handler", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); const secondMW = getConcatMiddleware("second") as InitializeMiddleware; stack.add(secondMW, { name: "second" }); stack.addRelativeTo(getConcatMiddleware("first") as InitializeMiddleware, { @@ -75,7 +75,7 @@ describe("MiddlewareStack", () => { }); it("should allow adding middleware relatively", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); type MW = InitializeMiddleware; stack.addRelativeTo(getConcatMiddleware("H") as MW, { name: "H", @@ -145,7 +145,7 @@ describe("MiddlewareStack", () => { }); it("should allow cloning", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); const secondMiddleware = getConcatMiddleware("second") as InitializeMiddleware; stack.add(secondMiddleware); stack.add(getConcatMiddleware("first") as InitializeMiddleware, { @@ -174,14 +174,14 @@ describe("MiddlewareStack", () => { }); it("should allow combining stacks", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); stack.add(getConcatMiddleware("first") as InitializeMiddleware); stack.add(getConcatMiddleware("second") as InitializeMiddleware, { name: "second", priority: "low", }); - const secondStack = new MiddlewareStack(); + const secondStack = constructStack(); secondStack.add(getConcatMiddleware("fourth") as FinalizeRequestMiddleware, { step: "build", priority: "low", @@ -209,7 +209,7 @@ describe("MiddlewareStack", () => { }); it("should allow the removal of middleware by constructor identity", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); stack.add(getConcatMiddleware("don't remove me") as InitializeMiddleware, { name: "notRemove" }); stack.addRelativeTo(getConcatMiddleware("remove me!") as InitializeMiddleware, { relation: "after", @@ -231,7 +231,7 @@ describe("MiddlewareStack", () => { }); it("should allow the removal of middleware by tag", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); stack.add(getConcatMiddleware("not removed") as InitializeMiddleware, { name: "not removed", tags: ["foo", "bar"], @@ -256,7 +256,7 @@ describe("MiddlewareStack", () => { }); it("should apply customizations from pluggables", async () => { - const stack = new MiddlewareStack(); + const stack = constructStack(); const plugin: Pluggable = { applyToStack: (stack) => { stack.addRelativeTo(getConcatMiddleware("second") as InitializeMiddleware, { diff --git a/packages/middleware-stack/src/MiddlewareStack.ts b/packages/middleware-stack/src/MiddlewareStack.ts index d27784f44ac5..65484e429fba 100644 --- a/packages/middleware-stack/src/MiddlewareStack.ts +++ b/packages/middleware-stack/src/MiddlewareStack.ts @@ -1,24 +1,14 @@ import { AbsoluteLocation, - BuildHandlerOptions, - BuildMiddleware, DeserializeHandler, - DeserializeHandlerOptions, - DeserializeMiddleware, - FinalizeRequestHandlerOptions, - FinalizeRequestMiddleware, Handler, HandlerExecutionContext, HandlerOptions, - InitializeHandlerOptions, - InitializeMiddleware, MiddlewareStack as IMiddlewareStack, MiddlewareType, Pluggable, Priority, RelativeLocation, - SerializeHandlerOptions, - SerializeMiddleware, Step, } from "@aws-sdk/types"; @@ -32,200 +22,76 @@ import { RelativeMiddlewareEntry, } from "./types"; -export interface MiddlewareStack extends IMiddlewareStack {} - -export class MiddlewareStack { - private readonly absoluteEntries: Array> = []; - private readonly relativeEntries: Array> = []; - private entriesNameMap: { +export function constructStack(): IMiddlewareStack { + const absoluteEntries: Array> = []; + const relativeEntries: Array> = []; + const entriesNameMap: { [middlewareName: string]: MiddlewareEntry | RelativeMiddlewareEntry; } = {}; - add(middleware: InitializeMiddleware, options?: InitializeHandlerOptions & AbsoluteLocation): void; - - add(middleware: SerializeMiddleware, options: SerializeHandlerOptions & AbsoluteLocation): void; - - add(middleware: BuildMiddleware, options: BuildHandlerOptions & AbsoluteLocation): void; - - add( - middleware: FinalizeRequestMiddleware, - options: FinalizeRequestHandlerOptions & AbsoluteLocation - ): void; - - add(middleware: DeserializeMiddleware, options: DeserializeHandlerOptions & AbsoluteLocation): void; - - add(middleware: MiddlewareType, options: HandlerOptions & AbsoluteLocation = {}): void { - const { name, step = "initialize", tags, priority = "normal" } = options; - const entry: MiddlewareEntry = { - name, - step, - tags, - priority, - middleware, - }; - if (name) { - if (Object.prototype.hasOwnProperty.call(this.entriesNameMap, name)) { - throw new Error(`Duplicated middleware name '${name}'`); - } - this.entriesNameMap[name] = entry; - } - this.absoluteEntries.push(entry); - } - - addRelativeTo( - middleware: InitializeMiddleware, - options: InitializeHandlerOptions & RelativeLocation - ): void; - - addRelativeTo( - middleware: SerializeMiddleware, - options: SerializeHandlerOptions & RelativeLocation - ): void; - - addRelativeTo( - middleware: BuildMiddleware, - options: BuildHandlerOptions & RelativeLocation - ): void; - - addRelativeTo( - middleware: FinalizeRequestMiddleware, - options: FinalizeRequestHandlerOptions & RelativeLocation - ): void; - - addRelativeTo( - middleware: DeserializeMiddleware, - options: DeserializeHandlerOptions & RelativeLocation - ): void; - - addRelativeTo( - middleware: MiddlewareType, - options: HandlerOptions & RelativeLocation - ): void { - const { step = "initialize", name, tags, relation, toMiddleware } = options; - const entry: RelativeMiddlewareEntry = { - middleware, - step, - name, - tags, - next: relation === "before" ? toMiddleware : undefined, - prev: relation === "after" ? toMiddleware : undefined, - }; - if (name) { - if (Object.prototype.hasOwnProperty.call(this.entriesNameMap, name)) { - throw new Error(`Duplicated middleware name '${name}'`); - } - this.entriesNameMap[name] = entry; - } - this.relativeEntries.push(entry); - } - - private sort( + const sort = ( entries: Array | NormalizedRelativeEntry> - ): Array | NormalizedRelativeEntry> { + ): Array | NormalizedRelativeEntry> => //reverse before sorting so that middleware of same step will execute in //the order of being added - return entries.sort( + entries.sort( (a, b) => stepWeights[b.step] - stepWeights[a.step] || priorityWeights[b.priority || "normal"] - priorityWeights[a.priority || "normal"] ); - } - - clone(): IMiddlewareStack { - const clone = new MiddlewareStack(); - clone.absoluteEntries.push(...this.absoluteEntries); - clone.relativeEntries.push(...this.relativeEntries); - clone.entriesNameMap = { ...this.entriesNameMap }; - return clone; - } - concat( - from: IMiddlewareStack - ): MiddlewareStack { - const clone = new MiddlewareStack(); - clone.entriesNameMap = { ...(this.entriesNameMap as any) }; - // IMiddlewareStack interface doesn't contain private members variables - // like `entriesNameMap`, but in fact the function expects `MiddlewareStack` - // class instance. So here we cast it. - const _from = from as MiddlewareStack; - for (const name in _from.entriesNameMap) { - if (clone.entriesNameMap[name]) { - throw new Error(`Duplicated middleware name '${name}'`); - } - clone.entriesNameMap[name] = _from.entriesNameMap[name]; - } - clone.absoluteEntries.push(...(this.absoluteEntries as any), ..._from.absoluteEntries); - clone.relativeEntries.push(...(this.relativeEntries as any), ..._from.relativeEntries); - return clone; - } - - remove(toRemove: MiddlewareType | string): boolean { - if (typeof toRemove === "string") return this.removeByName(toRemove); - else return this.removeByReference(toRemove); - } - - private removeByName(toRemove: string): boolean { - for (let i = this.absoluteEntries.length - 1; i >= 0; i--) { - if (this.absoluteEntries[i].name && this.absoluteEntries[i].name === toRemove) { - this.absoluteEntries.splice(i, 1); - delete this.entriesNameMap[toRemove]; + const removeByName = (toRemove: string): boolean => { + for (let i = absoluteEntries.length - 1; i >= 0; i--) { + if (absoluteEntries[i].name && absoluteEntries[i].name === toRemove) { + absoluteEntries.splice(i, 1); + delete entriesNameMap[toRemove]; return true; } } - for (let i = this.relativeEntries.length - 1; i >= 0; i--) { - if (this.relativeEntries[i].name && this.relativeEntries[i].name === toRemove) { - this.relativeEntries.splice(i, 1); - delete this.entriesNameMap[toRemove]; + for (let i = relativeEntries.length - 1; i >= 0; i--) { + if (relativeEntries[i].name && relativeEntries[i].name === toRemove) { + relativeEntries.splice(i, 1); + delete entriesNameMap[toRemove]; return true; } } return false; - } + }; - private removeByReference(toRemove: MiddlewareType): boolean { - for (let i = this.absoluteEntries.length - 1; i >= 0; i--) { - if (this.absoluteEntries[i].middleware === toRemove) { - const { name } = this.absoluteEntries[i]; - if (name) delete this.entriesNameMap[name]; - this.absoluteEntries.splice(i, 1); + const removeByReference = (toRemove: MiddlewareType): boolean => { + for (let i = absoluteEntries.length - 1; i >= 0; i--) { + if (absoluteEntries[i].middleware === toRemove) { + const { name } = absoluteEntries[i]; + if (name) delete entriesNameMap[name]; + absoluteEntries.splice(i, 1); return true; } } - for (let i = this.relativeEntries.length - 1; i >= 0; i--) { - if (this.relativeEntries[i].middleware === toRemove) { - const { name } = this.relativeEntries[i]; - if (name) delete this.entriesNameMap[name]; - this.relativeEntries.splice(i, 1); + for (let i = relativeEntries.length - 1; i >= 0; i--) { + if (relativeEntries[i].middleware === toRemove) { + const { name } = relativeEntries[i]; + if (name) delete entriesNameMap[name]; + relativeEntries.splice(i, 1); return true; } } return false; - } - - removeByTag(toRemove: string): boolean { - let removed = false; - for (let i = this.absoluteEntries.length - 1; i >= 0; i--) { - const { tags, name } = this.absoluteEntries[i]; - if (tags && tags.indexOf(toRemove) > -1) { - this.absoluteEntries.splice(i, 1); - if (name) delete this.entriesNameMap[name]; - removed = true; - } - } - for (let i = this.relativeEntries.length - 1; i >= 0; i--) { - const { tags, name } = this.relativeEntries[i]; - if (tags && tags.indexOf(toRemove) > -1) { - this.relativeEntries.splice(i, 1); - if (name) delete this.entriesNameMap[name]; - removed = true; - } - } - return removed; - } + }; - use(plugin: Pluggable) { - plugin.applyToStack(this); - } + const cloneTo = ( + toStack: IMiddlewareStack + ): IMiddlewareStack => { + const clone = toStack || constructStack(); + absoluteEntries.forEach((entry) => { + //@ts-ignore + clone.add(entry.middleware, { ...entry }); + }); + relativeEntries.forEach((entry) => { + //@ts-ignore + clone.addRelativeTo(entry.middleware, { ...entry }); + }); + return clone; + }; /** * Resolve relative middleware entries to multiple double linked lists @@ -240,14 +106,14 @@ export class MiddlewareStack { * * The 2 types of linked list will return as a tuple */ - private normalizeRelativeEntries(): NormalizingEntryResult { - const absoluteMiddlewareNamesMap = this.absoluteEntries + const normalizeRelativeEntries = (): NormalizingEntryResult => { + const absoluteMiddlewareNamesMap = absoluteEntries .filter((entry) => entry.name) .reduce((accumulator, entry) => { accumulator[entry.name!] = entry; return accumulator; }, {} as NamedMiddlewareEntriesMap); - const normalized = this.relativeEntries.map( + const normalized = relativeEntries.map( (entry) => ({ ...entry, @@ -264,8 +130,8 @@ export class MiddlewareStack { }, {} as NamedRelativeEntriesMap); const anchors: RelativeMiddlewareAnchor = {}; - for (let i = 0; i < this.relativeEntries.length; i++) { - const { prev, next } = this.relativeEntries[i]; + for (let i = 0; i < relativeEntries.length; i++) { + const { prev, next } = relativeEntries[i]; const resolvedCurr = normalized[i]; //either prev or next is set if (prev) { @@ -323,7 +189,7 @@ export class MiddlewareStack { } } return [orphanedRelativeEntries, anchors]; - } + }; /** * Get a final list of middleware in the order of being executed in the resolved handler. @@ -334,11 +200,11 @@ export class MiddlewareStack { * 2. if `toMiddleware` doesn't exist in the specific `step`, the middleware will be appended * to specific `step` with priority of `normal` */ - private getMiddlewareList(): Array> { + const getMiddlewareList = (): Array> => { const middlewareList: Array> = []; - const [orphanedRelativeEntries, anchors] = this.normalizeRelativeEntries(); - let entryList = [...this.absoluteEntries, ...orphanedRelativeEntries]; - entryList = this.sort(entryList); + const [orphanedRelativeEntries, anchors] = normalizeRelativeEntries(); + let entryList = [...absoluteEntries, ...orphanedRelativeEntries]; + entryList = sort(entryList); for (const entry of entryList) { const defaultAnchorValue = { prev: undefined, next: undefined }; const { prev, next } = entry.name ? anchors[entry.name] || defaultAnchorValue : defaultAnchorValue; @@ -364,18 +230,89 @@ export class MiddlewareStack { } } return middlewareList.reverse(); - } - - resolve( - handler: DeserializeHandler, - context: HandlerExecutionContext - ): Handler { - for (const middleware of this.getMiddlewareList()) { - handler = middleware(handler as Handler, context) as any; - } + }; - return handler as Handler; - } + const stack = { + add: (middleware: MiddlewareType, options: HandlerOptions & AbsoluteLocation = {}) => { + const { name, step = "initialize", tags, priority = "normal" } = options; + const entry: MiddlewareEntry = { + name, + step, + tags, + priority, + middleware, + }; + if (name) { + if (Object.prototype.hasOwnProperty.call(entriesNameMap, name)) { + throw new Error(`Duplicated middleware name '${name}'`); + } + entriesNameMap[name] = entry; + } + absoluteEntries.push(entry); + }, + addRelativeTo: ( + middleware: MiddlewareType, + options: HandlerOptions & RelativeLocation + ) => { + const { step = "initialize", name, tags, relation, toMiddleware } = options; + const entry: RelativeMiddlewareEntry = { + middleware, + step, + name, + tags, + next: relation === "before" ? toMiddleware : undefined, + prev: relation === "after" ? toMiddleware : undefined, + }; + if (name) { + if (Object.prototype.hasOwnProperty.call(entriesNameMap, name)) { + throw new Error(`Duplicated middleware name '${name}'`); + } + entriesNameMap[name] = entry; + } + relativeEntries.push(entry); + }, + clone: (): IMiddlewareStack => cloneTo(constructStack()), + use: (plugin: Pluggable) => { + plugin.applyToStack(stack); + }, + remove: (toRemove: MiddlewareType | string): boolean => { + if (typeof toRemove === "string") return removeByName(toRemove); + else return removeByReference(toRemove); + }, + removeByTag: (toRemove: string): boolean => { + let removed = false; + for (let i = absoluteEntries.length - 1; i >= 0; i--) { + const { tags, name } = absoluteEntries[i]; + if (tags && tags.indexOf(toRemove) > -1) { + absoluteEntries.splice(i, 1); + if (name) delete entriesNameMap[name]; + removed = true; + } + } + for (let i = relativeEntries.length - 1; i >= 0; i--) { + const { tags, name } = relativeEntries[i]; + if (tags && tags.indexOf(toRemove) > -1) { + relativeEntries.splice(i, 1); + if (name) delete entriesNameMap[name]; + removed = true; + } + } + return removed; + }, + concat: ( + from: IMiddlewareStack + ): IMiddlewareStack => cloneTo(from.clone()), + resolve: ( + handler: DeserializeHandler, + context: HandlerExecutionContext + ): Handler => { + for (const middleware of getMiddlewareList()) { + handler = middleware(handler as Handler, context) as any; + } + return handler as Handler; + }, + }; + return stack; } const stepWeights: { [key in Step]: number } = { diff --git a/packages/smithy-client/src/client.ts b/packages/smithy-client/src/client.ts index b2ff43dc83b2..5112a91b3290 100644 --- a/packages/smithy-client/src/client.ts +++ b/packages/smithy-client/src/client.ts @@ -1,4 +1,4 @@ -import { MiddlewareStack } from "@aws-sdk/middleware-stack"; +import { constructStack } from "@aws-sdk/middleware-stack"; import { Client as IClient, Command, MetadataBearer, RequestHandler } from "@aws-sdk/types"; export interface SmithyConfiguration { @@ -14,7 +14,7 @@ export class Client< ClientOutput extends MetadataBearer, ResolvedClientConfiguration extends SmithyResolvedConfiguration > implements IClient { - public middlewareStack = new MiddlewareStack(); + public middlewareStack = constructStack(); readonly config: ResolvedClientConfiguration; constructor(config: ResolvedClientConfiguration) { this.config = config; diff --git a/packages/smithy-client/src/command.ts b/packages/smithy-client/src/command.ts index 9295f6192887..f7065e4f2485 100644 --- a/packages/smithy-client/src/command.ts +++ b/packages/smithy-client/src/command.ts @@ -1,4 +1,4 @@ -import { MiddlewareStack } from "@aws-sdk/middleware-stack"; +import { constructStack } from "@aws-sdk/middleware-stack"; import { Command as ICommand, Handler, MetadataBearer, MiddlewareStack as IMiddlewareStack } from "@aws-sdk/types"; export abstract class Command< @@ -9,9 +9,9 @@ export abstract class Command< ClientOutput extends MetadataBearer = any > implements ICommand { abstract input: Input; - readonly middlewareStack: IMiddlewareStack = new MiddlewareStack(); + readonly middlewareStack: IMiddlewareStack = constructStack(); abstract resolveMiddleware( - stack: MiddlewareStack, + stack: IMiddlewareStack, configuration: ResolvedClientConfiguration, options: any ): Handler; diff --git a/packages/util-create-request/src/foo.fixture.ts b/packages/util-create-request/src/foo.fixture.ts index bc58cb448b48..8361f7b86ac9 100644 --- a/packages/util-create-request/src/foo.fixture.ts +++ b/packages/util-create-request/src/foo.fixture.ts @@ -1,7 +1,7 @@ -import { MiddlewareStack } from "@aws-sdk/middleware-stack"; +import { constructStack } from "@aws-sdk/middleware-stack"; import { HttpRequest } from "@aws-sdk/protocol-http"; import { Client, Command } from "@aws-sdk/smithy-client"; -import { MetadataBearer } from "@aws-sdk/types"; +import { MetadataBearer, MiddlewareStack } from "@aws-sdk/types"; export interface OperationInput { String: string; @@ -22,14 +22,14 @@ const input: OperationInput = { String: "input" }; export const fooClient: Client = { config: {}, - middlewareStack: new MiddlewareStack(), + middlewareStack: constructStack(), send: (command: Command) => command.resolveMiddleware(this.middlewareStack, this.config, undefined)({ input }), destroy: () => {}, }; export const operationCommand: Command = { - middlewareStack: new MiddlewareStack(), + middlewareStack: constructStack(), input: {} as any, resolveMiddleware: (stack: MiddlewareStack) => { const concatStack = stack.concat(operationCommand.middlewareStack);