From 33174ee1a72bb86f8cfec177d8e2136a20722bcd Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Tue, 27 Oct 2020 11:42:55 +0100 Subject: [PATCH] improve types for progress event --- python/rpdk/typescript/templates/README.md | 2 +- python/rpdk/typescript/templates/handlers.ts | 22 ++++----- src/exceptions.ts | 6 +-- src/resource.ts | 47 ++++++++++++-------- tests/lib/proxy.test.ts | 12 ++--- tests/lib/resource.test.ts | 38 ++++++++-------- 6 files changed, 68 insertions(+), 59 deletions(-) diff --git a/python/rpdk/typescript/templates/README.md b/python/rpdk/typescript/templates/README.md index 27dbe66..e837451 100644 --- a/python/rpdk/typescript/templates/README.md +++ b/python/rpdk/typescript/templates/README.md @@ -10,7 +10,7 @@ Congratulations on starting development! Next steps: Implement CloudFormation resource here. Each function must always return a ProgressEvent. ```typescript -const progress: ProgressEvent = ProgressEvent.builder>() +const progress = ProgressEvent.builder>() // Required // Must be one of OperationStatus.InProgress, OperationStatus.Failed, OperationStatus.Success diff --git a/python/rpdk/typescript/templates/handlers.ts b/python/rpdk/typescript/templates/handlers.ts index 4009152..a7ba230 100644 --- a/python/rpdk/typescript/templates/handlers.ts +++ b/python/rpdk/typescript/templates/handlers.ts @@ -32,8 +32,8 @@ class Resource extends BaseResource { request: ResourceHandlerRequest, callbackContext: CallbackContext, logger: LoggerProxy - ): Promise { - const model: ResourceModel = new ResourceModel(request.desiredResourceState); + ): Promise> { + const model = new ResourceModel(request.desiredResourceState); const progress = ProgressEvent.progress>(model); // TODO: put code here @@ -69,8 +69,8 @@ class Resource extends BaseResource { request: ResourceHandlerRequest, callbackContext: CallbackContext, logger: LoggerProxy - ): Promise { - const model: ResourceModel = new ResourceModel(request.desiredResourceState); + ): Promise> { + const model = new ResourceModel(request.desiredResourceState); const progress = ProgressEvent.progress>(model); // TODO: put code here progress.status = OperationStatus.Success; @@ -93,8 +93,8 @@ class Resource extends BaseResource { request: ResourceHandlerRequest, callbackContext: CallbackContext, logger: LoggerProxy - ): Promise { - const model: ResourceModel = new ResourceModel(request.desiredResourceState); + ): Promise> { + const model = new ResourceModel(request.desiredResourceState); const progress = ProgressEvent.progress>(); // TODO: put code here progress.status = OperationStatus.Success; @@ -116,8 +116,8 @@ class Resource extends BaseResource { request: ResourceHandlerRequest, callbackContext: CallbackContext, logger: LoggerProxy - ): Promise { - const model: ResourceModel = new ResourceModel(request.desiredResourceState); + ): Promise> { + const model = new ResourceModel(request.desiredResourceState); // TODO: put code here const progress = ProgressEvent.success>(model); return progress; @@ -138,8 +138,8 @@ class Resource extends BaseResource { request: ResourceHandlerRequest, callbackContext: CallbackContext, logger: LoggerProxy - ): Promise { - const model: ResourceModel = new ResourceModel(request.desiredResourceState); + ): Promise> { + const model = new ResourceModel(request.desiredResourceState); // TODO: put code here const progress = ProgressEvent.builder>() .status(OperationStatus.Success) @@ -149,7 +149,7 @@ class Resource extends BaseResource { } } -const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel); +export const resource = new Resource(ResourceModel.TYPE_NAME, ResourceModel); export const entrypoint = resource.entrypoint; diff --git a/src/exceptions.ts b/src/exceptions.ts index 85fe377..1c776af 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -1,4 +1,4 @@ -import { HandlerErrorCode } from './interface'; +import { BaseModel, HandlerErrorCode } from './interface'; import { ProgressEvent } from './proxy'; export abstract class BaseHandlerException extends Error { @@ -13,8 +13,8 @@ export abstract class BaseHandlerException extends Error { Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain } - public toProgressEvent(): ProgressEvent { - return ProgressEvent.failed(this.errorCode, this.toString()); + public toProgressEvent(): ProgressEvent { + return ProgressEvent.failed>(this.errorCode, this.toString()); } } diff --git a/src/resource.ts b/src/resource.ts index 4703dfd..2757adc 100644 --- a/src/resource.ts +++ b/src/resource.ts @@ -41,11 +41,14 @@ const MUTATING_ACTIONS: [Action, Action, Action] = [ Action.Delete, ]; -export type HandlerSignature = Callable< +export type HandlerSignature = Callable< [Optional, any, Dict, LoggerProxy], - Promise + Promise> >; -export class HandlerSignatures extends Map {} +export class HandlerSignatures extends Map< + Action, + HandlerSignature +> {} class HandlerEvents extends Map {} /** @@ -69,8 +72,8 @@ function ensureSerialize(toResponse = false): MethodDecorat descriptor.value = async function ( event: any | Dict, context: any - ): Promise> { - const progress: ProgressEvent = await originalMethod.apply(this, [ + ): Promise | CfnResponse> { + const progress: ProgressEvent = await originalMethod.apply(this, [ event, context, ]); @@ -108,10 +111,10 @@ export abstract class BaseResource { constructor( public readonly typeName: string, public readonly modelTypeReference: Constructor, - private handlers?: HandlerSignatures + private handlers?: HandlerSignatures ) { this.typeName = typeName || ''; - this.handlers = handlers || new HandlerSignatures(); + this.handlers = handlers || new HandlerSignatures(); this.lambdaLogger = console; @@ -256,7 +259,10 @@ export abstract class BaseResource { } } - public addHandler = (action: Action, f: HandlerSignature): HandlerSignature => { + public addHandler = ( + action: Action, + f: HandlerSignature + ): HandlerSignature => { this.handlers.set(action, f); return f; }; @@ -266,12 +272,12 @@ export abstract class BaseResource { request: BaseResourceHandlerRequest, action: Action, callbackContext: Dict - ): Promise => { + ): Promise> => { const actionName = action == null ? '' : action.toString(); if (!this.handlers.has(action)) { throw new Error(`Unknown action ${actionName}`); } - const handle: HandlerSignature = this.handlers.get(action); + const handle: HandlerSignature = this.handlers.get(action); // We will make the callback context and resource states readonly // to avoid modification at a later time deepFreeze(callbackContext); @@ -328,15 +334,15 @@ export abstract class BaseResource { public async testEntrypoint( eventData: any | Dict, context?: any - ): Promise; + ): Promise>; @boundMethod @ensureSerialize() public async testEntrypoint( eventData: Dict, context?: any - ): Promise { + ): Promise> { let msg = 'Uninitialized'; - let progress: ProgressEvent; + let progress: ProgressEvent; try { if (!this.modelTypeReference) { throw new exceptions.InternalFailure( @@ -359,11 +365,14 @@ export abstract class BaseResource { err.stack = `${new Error().stack}\n${err.stack}`; if (err instanceof BaseHandlerException) { this.log(`Handler error: ${err.message}`, err); - progress = err.toProgressEvent(); + progress = err.toProgressEvent(); } else { this.log(`Exception caught: ${err.message}`, err); msg = err.message || msg; - progress = ProgressEvent.failed(HandlerErrorCode.InternalFailure, msg); + progress = ProgressEvent.failed>( + HandlerErrorCode.InternalFailure, + msg + ); } } return Promise.resolve(progress); @@ -431,8 +440,8 @@ export abstract class BaseResource { public async entrypoint( eventData: Dict, context: LambdaContext - ): Promise { - let progress: ProgressEvent; + ): Promise> { + let progress: ProgressEvent; let bearerToken: string; try { if (!this.modelTypeReference) { @@ -498,10 +507,10 @@ export abstract class BaseResource { err.stack = `${new Error().stack}\n${err.stack}`; if (err instanceof BaseHandlerException) { this.log(`Handler error: ${err.message}`, err); - progress = err.toProgressEvent(); + progress = err.toProgressEvent(); } else { this.log(`Exception caught: ${err.message}`, err); - progress = ProgressEvent.failed( + progress = ProgressEvent.failed>( HandlerErrorCode.InternalFailure, err.message ); diff --git a/tests/lib/proxy.test.ts b/tests/lib/proxy.test.ts index ab3d5bc..fe03525 100644 --- a/tests/lib/proxy.test.ts +++ b/tests/lib/proxy.test.ts @@ -21,7 +21,7 @@ describe('when getting session proxy', () => { }); test('get session returns proxy', () => { - const proxy: SessionProxy = SessionProxy.getSession({ + const proxy = SessionProxy.getSession({ accessKeyId: '', secretAccessKey: '', sessionToken: '', @@ -31,14 +31,14 @@ describe('when getting session proxy', () => { }); test('get session returns null', () => { - const proxy: SessionProxy = SessionProxy.getSession(null); + const proxy = SessionProxy.getSession(null); expect(proxy).toBeNull(); }); test('progress event failed is json serializable', () => { - const errorCode: HandlerErrorCode = HandlerErrorCode.AlreadyExists; + const errorCode = HandlerErrorCode.AlreadyExists; const message = 'message of failed event'; - const event: ProgressEvent = ProgressEvent.failed(errorCode, message); + const event = ProgressEvent.failed(errorCode, message); expect(event.status).toBe(OperationStatus.Failed); expect(event.errorCode).toBe(errorCode); expect(event.message).toBe(message); @@ -76,7 +76,7 @@ describe('when getting session proxy', () => { someotherkey: 'b', somenullkey: null, }); - const event = ProgressEvent.progress(model, null); + const event = ProgressEvent.progress>(model, null); event.message = message; const serialized = event.serialize(); expect(serialized).toMatchObject({ @@ -102,7 +102,7 @@ describe('when getting session proxy', () => { someotherkey: 'd', }), ]; - const event = new ProgressEvent({ + const event = new ProgressEvent({ status: OperationStatus.Success, message, resourceModels: models, diff --git a/tests/lib/resource.test.ts b/tests/lib/resource.test.ts index 2c4e2d1..36db98a 100644 --- a/tests/lib/resource.test.ts +++ b/tests/lib/resource.test.ts @@ -42,11 +42,11 @@ describe('when getting resource', () => { let spySessionClient: jest.SpyInstance; let spyInitializeRuntime: jest.SpyInstance; const TYPE_NAME = 'Test::Foo::Bar'; - class Resource extends BaseResource {} class MockModel extends SimpleStateModel { ['constructor']: typeof MockModel; public static readonly TYPE_NAME: string = TYPE_NAME; } + class Resource extends BaseResource {} beforeEach(() => { const mockCloudformation = (CloudFormation as unknown) as jest.Mock; @@ -120,7 +120,7 @@ describe('when getting resource', () => { jest.restoreAllMocks(); }); - const getResource = (handlers?: HandlerSignatures) => { + const getResource = (handlers?: HandlerSignatures): Resource => { const instance = new Resource(TYPE_NAME, MockModel, handlers); return instance; }; @@ -242,7 +242,7 @@ describe('when getting resource', () => { test('entrypoint with callback context', async () => { entrypointPayload['callbackContext'] = { a: 'b' }; - const event: ProgressEvent = ProgressEvent.success(null, { c: 'd' }); + const event = ProgressEvent.success(null, { c: 'd' }); const mockHandler: jest.Mock = jest.fn(() => event); const resource = new Resource(TYPE_NAME, MockModel); resource.addHandler(Action.Create, mockHandler); @@ -263,7 +263,7 @@ describe('when getting resource', () => { test('entrypoint without callback context', async () => { entrypointPayload['callbackContext'] = null; - const event: ProgressEvent = ProgressEvent.progress(null, { c: 'd' }); + const event = ProgressEvent.progress(null, { c: 'd' }); event.callbackDelaySeconds = 5; const mockHandler: jest.Mock = jest.fn(() => event); const resource = new Resource(TYPE_NAME, MockModel); @@ -435,7 +435,7 @@ describe('when getting resource', () => { }); test('add handler', () => { - class ResourceEventHandler extends BaseResource { + class ResourceEventHandler extends BaseResource { @handlerEvent(Action.Create) public create(): void {} @handlerEvent(Action.Read) @@ -447,7 +447,7 @@ describe('when getting resource', () => { @handlerEvent(Action.List) public list(): void {} } - const handlers: HandlerSignatures = new HandlerSignatures(); + const handlers = new HandlerSignatures(); const resource = new ResourceEventHandler(null, null, handlers); expect(resource['handlers'].get(Action.Create)).toBe(resource.create); expect(resource['handlers'].get(Action.Read)).toBe(resource.read); @@ -459,17 +459,17 @@ describe('when getting resource', () => { test('check resource instance and type name', async () => { class ResourceEventHandler extends BaseResource { @handlerEvent(Action.Create) - public async create(): Promise { - const progress: ProgressEvent = ProgressEvent.builder() + public async create(): Promise> { + const progress = ProgressEvent.builder>() .message(this.typeName) .status(OperationStatus.Success) .resourceModels([]) - .build() as ProgressEvent; + .build(); return progress; } } const spyCreate = jest.spyOn(ResourceEventHandler.prototype, 'create'); - const handlers: HandlerSignatures = new HandlerSignatures(); + const handlers = new HandlerSignatures(); const resource = new ResourceEventHandler(TYPE_NAME, MockModel, handlers); const event = await resource.testEntrypoint(testEntrypointPayload, null); expect(spyCreate).toHaveReturnedTimes(1); @@ -491,13 +491,13 @@ describe('when getting resource', () => { }); test('invoke handler was found', async () => { - const event: ProgressEvent = ProgressEvent.progress(); + const event = ProgressEvent.progress(); const mockHandler: jest.Mock = jest.fn(() => event); - const handlers: HandlerSignatures = new HandlerSignatures(); + const handlers = new HandlerSignatures(); handlers.set(Action.Create, mockHandler); const resource = getResource(handlers); const session = new SessionProxy({}); - const request = new BaseResourceHandlerRequest(); + const request = new BaseResourceHandlerRequest(); const callbackContext = {}; const response = await resource['invokeHandler']( session, @@ -519,7 +519,7 @@ describe('when getting resource', () => { const promises: any[] = []; [Action.List, Action.Read].forEach(async (action: Action) => { const mockHandler: jest.Mock = jest.fn(() => ProgressEvent.progress()); - const handlers: HandlerSignatures = new HandlerSignatures(); + const handlers = new HandlerSignatures(); handlers.set(action, mockHandler); const resource = getResource(handlers); const callbackContext = {}; @@ -540,9 +540,9 @@ describe('when getting resource', () => { }); test('invoke handler try object modification', async () => { - const event: ProgressEvent = ProgressEvent.progress(); + const event = ProgressEvent.progress(); const mockHandler: jest.Mock = jest.fn(() => event); - const handlers: HandlerSignatures = new HandlerSignatures(); + const handlers = new HandlerSignatures(); handlers.set(Action.Create, mockHandler); const resource = getResource(handlers); const callbackContext = { @@ -633,7 +633,7 @@ describe('when getting resource', () => { test('test entrypoint handler error', async () => { const resource = getResource(); - const event: ProgressEvent = await resource.testEntrypoint({}, null); + const event = await resource.testEntrypoint({}, null); expect(event.status).toBe(OperationStatus.Failed); expect(event.errorCode).toBe(HandlerErrorCode.InternalFailure); }); @@ -644,7 +644,7 @@ describe('when getting resource', () => { mockParseRequest.mockImplementationOnce(() => { throw { message: 'exception' }; }); - const event: ProgressEvent = await resource.testEntrypoint({}, null); + const event = await resource.testEntrypoint({}, null); expect(event.status).toBe(OperationStatus.Failed); expect(event.errorCode).toBe(HandlerErrorCode.InternalFailure); expect(event.message).toBe('exception'); @@ -664,7 +664,7 @@ describe('when getting resource', () => { const spyDeserialize: jest.SpyInstance = jest.spyOn(MockModel, 'deserialize'); const resource = new Resource(TYPE_NAME, MockModel); - const progressEvent: ProgressEvent = ProgressEvent.progress(); + const progressEvent = ProgressEvent.progress(); const mockHandler: jest.Mock = jest.fn(() => progressEvent); resource.addHandler(Action.Create, mockHandler); const event = await resource.testEntrypoint(testEntrypointPayload, null);