diff --git a/packages/config-resolver/src/index.ts b/packages/config-resolver/src/index.ts index 08ac7cdee7e..011fee2f8ec 100644 --- a/packages/config-resolver/src/index.ts +++ b/packages/config-resolver/src/index.ts @@ -5,10 +5,16 @@ import { export type IndexedObject = {[key: string]: any}; -export function resolveConfiguration( +export function resolveConfiguration< + T extends IndexedObject, + R extends T, + Input extends object, + Output extends object, + Stream = Uint8Array +>( providedConfiguration: T, configurationDefinition: ConfigurationDefinition, - middlewareStack: MiddlewareStack + middlewareStack: MiddlewareStack ): R { const out: Partial = {}; diff --git a/packages/middleware-content-length/src/index.ts b/packages/middleware-content-length/src/index.ts index 7d42b4b986a..57e472c5d5a 100644 --- a/packages/middleware-content-length/src/index.ts +++ b/packages/middleware-content-length/src/index.ts @@ -1,29 +1,40 @@ import { + BuildHandler, + BuildHandlerArguments, + BuildMiddleware, BodyLengthCalculator, - Handler, - HandlerArguments } from '@aws/types'; -export class ContentLengthMiddleware implements Handler { - constructor( - private readonly bodyLengthCalculator: BodyLengthCalculator, - private readonly next: Handler - ) {} - - async handle(args: HandlerArguments): Promise { - const request = args.request; - +export function contentLengthMiddleware< + Input extends object, + Output extends object, + Stream +>( + bodyLengthCalculator: BodyLengthCalculator +): BuildMiddleware { + return ( + next: BuildHandler + ): BuildHandler => async ( + args: BuildHandlerArguments + ): Promise => { + const {request} = args; if (!request) { throw new Error('Unable to determine request content-length due to missing request.'); } - if (request.body) { - request.headers['Content-Length'] = String(this.bodyLengthCalculator(request.body)); + const {body, headers} = request; + if ( + body && + Object.keys(headers) + .map(str => str.toLowerCase()) + .indexOf('content-length') === -1 + ) { + headers['Content-Length'] = String(bodyLengthCalculator(body)); } - return this.next.handle({ - request, - ...args + return next({ + ...args, + request }); } -} \ No newline at end of file +} diff --git a/packages/middleware-stack/src/index.spec.ts b/packages/middleware-stack/src/index.spec.ts index 0ddded96280..88497b95616 100644 --- a/packages/middleware-stack/src/index.spec.ts +++ b/packages/middleware-stack/src/index.spec.ts @@ -2,23 +2,20 @@ import {MiddlewareStack} from "./"; import { Handler, HandlerArguments, + FinalizeHandlerArguments, } from "@aws/types"; type input = Array; type output = object; -class ConcatMiddleware implements Handler { - constructor( - private readonly message: string, - private readonly next: Handler - ) {} - - handle(args: HandlerArguments): Promise { - return this.next.handle({ - ...args, - input: args.input.concat(this.message), - }) - } +function concatMiddleware( + message: string, + next: Handler +): Handler { + return (args: HandlerArguments): Promise => next({ + ...args, + input: args.input.concat(message), + }); } function shuffle(arr: Array): Array { @@ -36,16 +33,16 @@ describe('MiddlewareStack', () => { const stack = new MiddlewareStack(); const middleware = shuffle([ - [ConcatMiddleware.bind(null, 'second')], - [ConcatMiddleware.bind(null, 'first'), {priority: 10}], - [ConcatMiddleware.bind(null, 'fourth'), {step: 'build'}], + [concatMiddleware.bind(null, 'second')], + [concatMiddleware.bind(null, 'first'), {priority: 10}], + [concatMiddleware.bind(null, 'fourth'), {step: 'build'}], [ - ConcatMiddleware.bind(null, 'third'), + concatMiddleware.bind(null, 'third'), {step: 'build', priority: 1} ], - [ConcatMiddleware.bind(null, 'fifth'), {step: 'finalize'}], + [concatMiddleware.bind(null, 'fifth'), {step: 'finalize'}], [ - ConcatMiddleware.bind(null, 'sixth'), + concatMiddleware.bind(null, 'sixth'), {step: 'finalize', priority: -1} ], ]); @@ -54,128 +51,126 @@ describe('MiddlewareStack', () => { stack.add(mw, options); } - const inner = { - handle: jest.fn(({input}: HandlerArguments) => { - expect(input).toEqual([ - 'first', - 'second', - 'third', - 'fourth', - 'fifth', - 'sixth', - ]); - return {}; - }) - }; + const inner = jest.fn(({input}: FinalizeHandlerArguments) => { + expect(input).toEqual([ + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + ]); + return {}; + }); + const composed = stack.resolve(inner, {} as any); - await composed.handle({input: []}); + await composed({input: []}); - expect(inner.handle.mock.calls.length).toBe(1); + expect(inner.mock.calls.length).toBe(1); }); it('should allow cloning', async () => { const stack = new MiddlewareStack(); - stack.add(ConcatMiddleware.bind(null, 'second')); - stack.add(ConcatMiddleware.bind(null, 'first'), {priority: 100}); + stack.add(concatMiddleware.bind(null, 'second')); + stack.add(concatMiddleware.bind(null, 'first'), {priority: 100}); const secondStack = stack.clone(); - let inner = { - handle: jest.fn(({input}: HandlerArguments) => { - expect(input).toEqual([ - 'first', - 'second', - ]); - return Promise.resolve({}); - }) - }; - await secondStack.resolve(inner, {} as any).handle({input: []}); - expect(inner.handle.mock.calls.length).toBe(1); + let inner = jest.fn(({input}: FinalizeHandlerArguments) => { + expect(input).toEqual([ + 'first', + 'second', + ]); + return Promise.resolve({}); + }); + await secondStack.resolve(inner, {} as any)({input: []}); + expect(inner.mock.calls.length).toBe(1); }); it('should allow combining stacks', async () => { const stack = new MiddlewareStack(); - stack.add(ConcatMiddleware.bind(null, 'second')); - stack.add(ConcatMiddleware.bind(null, 'first'), {priority: 100}); + stack.add(concatMiddleware.bind(null, 'second')); + stack.add(concatMiddleware.bind(null, 'first'), {priority: 100}); const secondStack = new MiddlewareStack(); - secondStack.add(ConcatMiddleware.bind(null, 'fourth'), {step: 'build'}); + secondStack.add(concatMiddleware.bind(null, 'fourth'), {step: 'build'}); secondStack.add( - ConcatMiddleware.bind(null, 'third'), + concatMiddleware.bind(null, 'third'), {step: 'build', priority: 100} ); - let inner = { - handle: jest.fn(({input}: HandlerArguments) => { - expect(input).toEqual([ - 'first', - 'second', - 'third', - 'fourth', - ]); - return Promise.resolve({}); - }) - }; - await stack.concat(secondStack).resolve(inner, {} as any) - .handle({input: []}); - - expect(inner.handle.mock.calls.length).toBe(1); + let inner = jest.fn(({input}: FinalizeHandlerArguments) => { + expect(input).toEqual([ + 'first', + 'second', + 'third', + 'fourth', + ]); + return Promise.resolve({}); + }); + await stack.concat(secondStack).resolve(inner, {} as any)({input: []}); + + expect(inner.mock.calls.length).toBe(1); }); it('should allow the removal of middleware by constructor identity', async () => { - const MyMiddleware = ConcatMiddleware.bind(null, 'remove me!'); + const MyMiddleware = concatMiddleware.bind(null, 'remove me!'); const stack = new MiddlewareStack(); stack.add(MyMiddleware); - stack.add(ConcatMiddleware.bind(null, "don't remove me")); + stack.add(concatMiddleware.bind(null, "don't remove me")); - await stack.resolve({ - handle: ({input}: HandlerArguments>) => { + await stack.resolve( + ({input}: FinalizeHandlerArguments>) => { expect(input.sort()).toEqual([ "don't remove me", 'remove me!', ]); return Promise.resolve({}); - } - }, {} as any).handle({input: []}); + }, + {} as any + )({input: []}); stack.remove(MyMiddleware); - await stack.resolve({ - handle: ({input}: HandlerArguments>) => { + await stack.resolve( + ({input}: FinalizeHandlerArguments>) => { expect(input).toEqual(["don't remove me"]); return Promise.resolve({}); - } - }, {} as any).handle({input: []}); + }, + {} as any + )({input: []}); }); it('should allow the removal of middleware by tag', async () => { const stack = new MiddlewareStack(); stack.add( - ConcatMiddleware.bind(null, 'not removed'), + concatMiddleware.bind(null, 'not removed'), {tags: new Set(['foo', 'bar'])} ); stack.add( - ConcatMiddleware.bind(null, 'remove me!'), + concatMiddleware.bind(null, 'remove me!'), {tags: new Set(['foo', 'bar', 'baz'])} ); - await stack.resolve({ - handle: ({input}: HandlerArguments>) => { + await stack.resolve( + ({input}: FinalizeHandlerArguments>) => { expect(input.sort()).toEqual([ 'not removed', 'remove me!', ]); return Promise.resolve({}); - } - }, {} as any).handle({input: []}); + }, + {} as any + )({input: []}); stack.remove('baz'); - await stack.resolve({ - handle: ({input}: HandlerArguments>) => { + await stack.resolve( + ({input}: FinalizeHandlerArguments>) => { expect(input).toEqual(['not removed']); return Promise.resolve({}); - } - }, {} as any).handle({input: []}); + }, + {} as any + )({input: []}); }); }); diff --git a/packages/middleware-stack/src/index.ts b/packages/middleware-stack/src/index.ts index 5a4fa085975..5c274437805 100644 --- a/packages/middleware-stack/src/index.ts +++ b/packages/middleware-stack/src/index.ts @@ -1,4 +1,5 @@ import { + FinalizeHandler, Handler, HandlerOptions, Middleware, @@ -9,28 +10,26 @@ import { export type Step = 'initialize'|'build'|'finalize'; interface HandlerListEntry< - InputType extends object, - OutputType extends object, - StreamType + Input extends object, + Output extends object, + Stream > { step: Step; priority: number; - middleware: Middleware; + middleware: Middleware; tags?: Set; } export class MiddlewareStack< - InputType extends object, - OutputType extends object, - StreamType = Uint8Array -> implements IMiddlewareStack { - private readonly entries: Array< - HandlerListEntry - > = []; - private sorted: boolean = false; + Input extends object, + Output extends object, + Stream = Uint8Array +> implements IMiddlewareStack { + private readonly entries: Array> = []; + private sorted: boolean = true; add( - middleware: Middleware, + middleware: Middleware, options: HandlerOptions = {} ): void { const {step = 'initialize', priority = 0, tags} = options; @@ -43,23 +42,26 @@ export class MiddlewareStack< }); } - clone(): MiddlewareStack { - const clone = new MiddlewareStack(); + clone(): IMiddlewareStack { + const clone = new MiddlewareStack(); clone.entries.push(...this.entries); + clone.sorted = this.sorted; return clone; } - concat( - from: MiddlewareStack - ): MiddlewareStack { - const clone = new MiddlewareStack(); - clone.entries.push(...this.entries, ...from.entries); + concat< + InputType extends Input, + OutputType extends Output + >( + from: MiddlewareStack + ): MiddlewareStack { + const clone = new MiddlewareStack(); + clone.entries.push(...this.entries as any, ...from.entries); + clone.sorted = false; return clone; } - remove( - toRemove: Middleware|string - ): boolean { + remove(toRemove: Middleware|string): boolean { const {length} = this.entries; if (typeof toRemove === 'string') { this.removeByTag(toRemove); @@ -70,24 +72,25 @@ export class MiddlewareStack< return this.entries.length < length; } - resolve( - handler: Handler, + resolve< + InputType extends Input, + OutputType extends Output + >( + handler: FinalizeHandler, context: HandlerExecutionContext - ): Handler { + ): Handler { if (!this.sorted) { this.sort(); } for (const {middleware} of this.entries) { - handler = new middleware(handler, context); + handler = middleware(handler as Handler, context) as any; } - return handler; + return handler as Handler; } - private removeByIdentity( - toRemove: Middleware - ) { + private removeByIdentity(toRemove: Middleware) { for (let i = this.entries.length - 1; i >= 0; i--) { if (this.entries[i].middleware === toRemove) { this.entries.splice(i, 1);