From f3ce31ea7ef3642ad63eef8a6f85e7557fb389cf Mon Sep 17 00:00:00 2001 From: Zachary Rosenthal Date: Sun, 28 Apr 2019 15:50:14 -0400 Subject: [PATCH 1/5] feat(Request and App handler): Add ability for custom request deserialization and improve request ob BREAKING CHANGE: Request class has changed --- index.ts | 7 +- src/app/IApp.ts | 4 +- src/app/app.ts | 50 ++++- src/app/dispatcher.ts | 14 +- src/app/index.ts | 1 - src/error/custom-error-handler.ts | 4 +- src/error/default-error-handler.ts | 9 +- src/interfaces/context.ts | 10 - src/interfaces/index.ts | 1 - src/request/default-body-deserializer.ts | 9 + src/request/event.ts | 37 ---- src/request/http-methods.ts | 9 + src/request/index.ts | 3 +- src/request/request-body-deserializer.ts | 6 + src/request/request-event.ts | 10 + src/request/request-interface.ts | 5 - src/request/request.ts | 123 ++++++----- src/response.ts | 15 -- src/routing/IRouter.ts | 26 ++- src/routing/router.ts | 75 +++---- test/app/app.test.ts | 258 ++++++++++++++--------- test/request/request.test.ts | 81 ++++--- tsconfig.json | 1 + 23 files changed, 419 insertions(+), 339 deletions(-) delete mode 100644 src/interfaces/context.ts create mode 100644 src/request/default-body-deserializer.ts delete mode 100644 src/request/event.ts create mode 100644 src/request/http-methods.ts create mode 100644 src/request/request-body-deserializer.ts create mode 100644 src/request/request-event.ts delete mode 100644 src/request/request-interface.ts diff --git a/index.ts b/index.ts index 6b312bf..736ffa5 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,6 @@ -export {App, IContainer, IApp} from "./src/app"; -export { Router, Route, MindlessRoute, RouteUrl, IRouteUrl, IRouter } from './src/routing'; -export { Context } from './src/interfaces'; -export { Request, Event, HttpMethods } from './src/request'; +export * from "./src/app"; +export * from './src/routing'; +export * from './src/request'; export { Response } from './src/response'; export { Middleware } from './src/middleware/middleware'; export { Controller } from './src/controller/controller'; diff --git a/src/app/IApp.ts b/src/app/IApp.ts index 8facc95..9989e2a 100644 --- a/src/app/IApp.ts +++ b/src/app/IApp.ts @@ -1,8 +1,8 @@ -import { Request } from '../request' import { GenericConstructor } from '../interfaces' +import { RequestEvent } from '../request/request' import { Response } from '../response' export interface IApp { resolve(constructor: GenericConstructor): T - handleRequest(request: Request): Promise + handleRequest(request: RequestEvent): Promise } diff --git a/src/app/app.ts b/src/app/app.ts index feb948f..c23fc40 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,9 +1,12 @@ import { CustomErrorHandler } from '../error/custom-error-handler' import { DefaultErrorHandler } from '../error/default-error-handler' import { GenericConstructor } from '../interfaces' -import { Request } from '../request' +import { Request, RequestEvent } from '../request' +import { DefaultBodyDeserializer } from '../request/default-body-deserializer' +import { RequestBodyDeserializer } from '../request/request-body-deserializer' import { Response } from '../response' import { IRouter } from '../routing' +import { RouteData } from '../routing/IRouter' import { Dispatcher } from './dispatcher' import { IApp } from './IApp' import { IContainer } from './IContainer' @@ -12,18 +15,32 @@ export class App implements IApp { constructor( protected container: IContainer, protected router: IRouter, - protected errorHandler: CustomErrorHandler = DefaultErrorHandler + protected errorHandler: CustomErrorHandler = DefaultErrorHandler, + protected requestBodyDeserializer: RequestBodyDeserializer = new DefaultBodyDeserializer() ) {} resolve(constructor: GenericConstructor): T { return this.container.resolve(constructor) } - async handleRequest(request: Request): Promise { + async handleRequest(event: RequestEvent): Promise { + const [data, error] = this.getRouteData(event) + + if (error !== undefined) { + return this.errorHandler(error, event) + } + // If error is undefined then data must be defined + + const request = this.createRequest(event, data!) + try { - const data = this.router.getRouteData(request) - await Dispatcher.dispatchMiddleware(this.container, request, data.route.middleware || []) - return await Dispatcher.dispatchController(this.container, request, data.route, data.params) + await Dispatcher.dispatchMiddleware(this.container, request, data!.route.middleware || []) + return await Dispatcher.dispatchController( + this.container, + request, + data!.route, + data!.methodParameters + ) } catch (e) { // Allow middleware and controller to reject with response object if (e instanceof Response) { @@ -32,4 +49,25 @@ export class App implements IApp { return this.errorHandler(e, request) } } + + protected getRouteData(event: RequestEvent): [RouteData, undefined] | [undefined, Error] { + try { + return [this.router.getRouteData(event), undefined] + } catch (e) { + return [undefined, e] + } + } + + protected createRequest(event: RequestEvent, data: RouteData): Request { + const bodyObj = this.requestBodyDeserializer.deserialize(event, data.metadata) + + return new Request( + event.path, + bodyObj, + data!.metadata, + data!.pathParameters, + event.queryStringParameters, + event.headers + ) + } } diff --git a/src/app/dispatcher.ts b/src/app/dispatcher.ts index a56d14d..88de5ff 100644 --- a/src/app/dispatcher.ts +++ b/src/app/dispatcher.ts @@ -38,13 +38,15 @@ export class Dispatcher { return request } - try { - return request.getOrFail(param) - } catch (e) { - throw new MindlessError( - `Unable to inject ${param} into ${route.controller.name} ${route.function}` - ) + const value = request.getPathParameter(param) || request.getQueryStringParameter(param) + + if (value !== undefined) { + return value } + + throw new MindlessError( + `Unable to inject ${param} into ${route.controller.name} ${route.function}` + ) } let subjectController: Controller diff --git a/src/app/index.ts b/src/app/index.ts index 99048db..6cb0134 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -1,4 +1,3 @@ -export { Dispatcher } from './dispatcher' export { IContainer } from './IContainer' export { IApp } from './IApp' export { App } from './app' diff --git a/src/error/custom-error-handler.ts b/src/error/custom-error-handler.ts index 98ef88d..3bbcb7b 100644 --- a/src/error/custom-error-handler.ts +++ b/src/error/custom-error-handler.ts @@ -1,4 +1,4 @@ -import { Request } from '../request' +import { Request, RequestEvent } from '../request' import { Response } from '../response' -export type CustomErrorHandler = (e: Error, r: Request) => Response +export type CustomErrorHandler = (e: Error, r: Request | RequestEvent) => Response diff --git a/src/error/default-error-handler.ts b/src/error/default-error-handler.ts index d29d031..c98168a 100644 --- a/src/error/default-error-handler.ts +++ b/src/error/default-error-handler.ts @@ -1,9 +1,12 @@ -import { Request } from '../request' +import { Request, RequestEvent } from '../request' import { Response } from '../response' import { CustomErrorHandler } from './custom-error-handler' -export const DefaultErrorHandler: CustomErrorHandler = (e: Error, request: Request): Response => { - console.error(e) +export const DefaultErrorHandler: CustomErrorHandler = ( + error: Error, + request: Request | RequestEvent +): Response => { + console.error({ request, error }) return new Response(500, { message: 'An error occurred, using default error handler (see docs to supply your own). Please check your logs' diff --git a/src/interfaces/context.ts b/src/interfaces/context.ts deleted file mode 100644 index c154e3a..0000000 --- a/src/interfaces/context.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface Context { - functionName: string - functionVersion: string - invokedFunctionArn: string - awsRequestId: string - logGroupName: string - logStreamName: string - identity: any // idk? - clientContext: any // idk? -} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index bda1d23..bc3a03b 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,2 +1 @@ -export { Context } from './context' export { GenericConstructor } from './generic' diff --git a/src/request/default-body-deserializer.ts b/src/request/default-body-deserializer.ts new file mode 100644 index 0000000..a41703b --- /dev/null +++ b/src/request/default-body-deserializer.ts @@ -0,0 +1,9 @@ +import { RouteMetadata } from '../routing/IRouter' +import { RequestBodyDeserializer } from './request-body-deserializer' +import { RequestEvent } from './request-event' + +export class DefaultBodyDeserializer implements RequestBodyDeserializer { + deserialize(event: RequestEvent, metadata: RouteMetadata): object { + return event.body + } +} diff --git a/src/request/event.ts b/src/request/event.ts deleted file mode 100644 index e0f4c09..0000000 --- a/src/request/event.ts +++ /dev/null @@ -1,37 +0,0 @@ -export enum HttpMethods { - GET, - POST, - PUT, - DELETE, - PATCH, - OPTIONS, - HEAD -} - -export interface Event { - headers: { [key: string]: string } - path: string - pathParameters: { [key: string]: string } - requestContext: { [key: string]: any } - resource: string - httpMethod: string - queryStringParameters: { [key: string]: any } - stageVariables: { [key: string]: any } - body: string - isOffline?: boolean -} - -/* -export class Event { - constructor( - protected headers: { [key: string]: string }, - protected pathParameters: { [key: string]: string }, - protected requestContext: { [key: string]: any }, - protected queryStringParameters: { [key: string]: any }, - protected stageVariables: { [key: string]: any }, - protected body: string, - path: string, - resource: string, - httpMethod: HttpMethods, - isOffline?: boolean - ) {} */ diff --git a/src/request/http-methods.ts b/src/request/http-methods.ts new file mode 100644 index 0000000..2806b63 --- /dev/null +++ b/src/request/http-methods.ts @@ -0,0 +1,9 @@ +export enum HttpMethods { + GET, + POST, + PUT, + DELETE, + PATCH, + OPTIONS, + HEAD +} diff --git a/src/request/index.ts b/src/request/index.ts index 407d4e0..e8a819c 100644 --- a/src/request/index.ts +++ b/src/request/index.ts @@ -1,2 +1,3 @@ +export { RequestEvent } from './request-event' export { Request } from './request' -export { Event, HttpMethods } from './event' +export { HttpMethods } from './http-methods' diff --git a/src/request/request-body-deserializer.ts b/src/request/request-body-deserializer.ts new file mode 100644 index 0000000..090f07e --- /dev/null +++ b/src/request/request-body-deserializer.ts @@ -0,0 +1,6 @@ +import { RouteMetadata } from '../routing/IRouter' +import { RequestEvent } from './request-event' + +export interface RequestBodyDeserializer { + deserialize(event: RequestEvent, metadata: RouteMetadata): object +} diff --git a/src/request/request-event.ts b/src/request/request-event.ts new file mode 100644 index 0000000..e0f4382 --- /dev/null +++ b/src/request/request-event.ts @@ -0,0 +1,10 @@ +import { HttpMethods } from './http-methods' + +export interface RequestEvent { + body: TBody + queryStringParameters: { [key: string]: string | string[] } + headers: { [key: string]: string | string[] } + + path: string + method: HttpMethods +} diff --git a/src/request/request-interface.ts b/src/request/request-interface.ts deleted file mode 100644 index 84abe3d..0000000 --- a/src/request/request-interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IRequest { - get(key: string): any // retrieve request input - header(key: string): string | undefined // retrieve request header - add(key: string, value: any): void // add request data -} diff --git a/src/request/request.ts b/src/request/request.ts index 0d5c9ef..48567f2 100644 --- a/src/request/request.ts +++ b/src/request/request.ts @@ -1,68 +1,82 @@ import { MindlessError } from '../error/mindless.error' -import { Event, HttpMethods } from './event' -import { IRequest } from './request-interface' - -export class Request implements IRequest { - public RouteMetaData: { [key: string]: any } = {} - protected _body: { [key: string]: any } - protected data: { [key: string]: any } = {} - - constructor(protected event: Event) { - if (event.body === '' || event.body == null) { - this._body = {} - } else { - this._body = JSON.parse(event.body) - } - if (this.event.pathParameters == null) { - this.event.pathParameters = {} - } - if (this.event.queryStringParameters == null) { - this.event.queryStringParameters = {} - } - if (this.event.headers == null) { - this.event.headers = {} - } +import { RouteMetadata } from '../routing/IRouter' +import { HttpMethods } from './http-methods' +import { RequestEvent } from './request-event' + +export class Request { + protected _pathParam: Readonly<{ [key: string]: string }> + protected _queryParam: Readonly<{ [key: string]: string | string[] }> + protected _header: Readonly<{ [key: string]: string | string[] }> + + protected _context = new Map() + + protected _routeMetadata: Readonly + protected _method: Readonly + + constructor( + protected _path: Readonly, + protected _body: Readonly, + routeMetadata: RouteMetadata, + pathParameters: { [keys: string]: string } = {}, + queryStringParameters: { [key: string]: string | string[] } = {}, + headers: { [key: string]: string | string[] } = {} + ) { + this._method = routeMetadata.method + this._pathParam = Object.freeze(pathParameters) + this._queryParam = Object.freeze(queryStringParameters) + this._header = Object.freeze(headers) + this._routeMetadata = Object.freeze(routeMetadata) + } + + get path() { + return this._path + } + get method() { + return this._method } - get path(): string { - return this.event.path + get body() { + return this._body + } + get routeMetadata() { + return this._routeMetadata } - get method(): HttpMethods { - return (HttpMethods as any)[this.event.httpMethod.toUpperCase()] + public getPathParameter(key: string) { + return this._pathParam[key] } - public getOrFail(key: string): any { - const value = this.get(key) - if (value) { + public getPathParameterOrFail(key: string) { + const value = this.getPathParameter(key) + + if (value !== undefined) { return value } - throw new MindlessError( - `Invalid key: '${key}', key not found in pathParameters, queryStringParameters, or Body parameters.` - ) + throw new MindlessError(`Invalid key: '${key}', key not found in path parameters`) } - public get(key: string): any { - if (typeof this.data[key] !== 'undefined') { - return this.data[key] - } - if (typeof this._body[key] !== 'undefined') { - return this._body[key] - } - if (typeof this.event.queryStringParameters[key] !== 'undefined') { - return this.event.queryStringParameters[key] + public getQueryStringParameter(key: string) { + return this._queryParam[key] + } + + public getQueryStringParameterOrFail(key: string) { + const value = this.getQueryStringParameter(key) + + if (value !== undefined) { + return value } - return undefined + throw new MindlessError(`Invalid key: '${key}', key not found in query string parameters`) } - public header(key: string): string | undefined { - return this.event.headers[key] + public getHeader(key: string) { + return this._header[key] } - public headerOrFail(key: string): string { - const value = this.header(key) + public getHeaderOrFaile(key: string) { + const value = this.getHeader(key) + if (value !== undefined) { return value } @@ -70,18 +84,11 @@ export class Request implements IRequest { throw new MindlessError(`Invalid key: '${key}', key not found in headers`) } - public add(key: string, val: any, overwrite: boolean = false): void { - if (overwrite || typeof this.data[key] === 'undefined') { - this.data[key] = val - return - } - - throw new MindlessError( - `The key '${key}' already exists, pass 'overwrite=true' or use a different key.` - ) + public addContext(key: string, value: TValue) { + this._context.set(key, value) } - public addMultiple(data: { [key: string]: any }) { - Object.keys(data).forEach(key => this.add(key, data[key])) + public getContext(key: string): TValue { + return this._context.get(key) } } diff --git a/src/response.ts b/src/response.ts index 7ca7e66..c1eab87 100644 --- a/src/response.ts +++ b/src/response.ts @@ -1,9 +1,3 @@ -export interface AWSLambdaIntegrationResponse { - statusCode: number - body: string - headers: { [key: string]: string } -} - export class Response { /** * @@ -16,13 +10,4 @@ export class Response { public body: { [key: string]: any } = {}, public headers: { [key: string]: string } = {} ) {} - - // TODO: remove this from the response class - public toAWSLambdaIntegrationResponse(): AWSLambdaIntegrationResponse { - return { - statusCode: this.statusCode, - body: JSON.stringify(this.body), - headers: this.headers - } - } } diff --git a/src/routing/IRouter.ts b/src/routing/IRouter.ts index 8544f99..0857e8f 100644 --- a/src/routing/IRouter.ts +++ b/src/routing/IRouter.ts @@ -1,15 +1,27 @@ import { Controller } from '../controller/controller' import { Middleware } from '../middleware/middleware' -import { Request } from '../request' +import { RequestEvent } from '../request/request-event' import { Route } from './routes' -export interface RouteData { - route: Route - params: string[] +type Omit = Pick> + +export type RouteMetadata< + TRoute extends Route = Route +> = Omit + +export interface RouteData< + TRoute extends Route = Route +> { + route: TRoute + metadata: RouteMetadata + pathParameters: { [key: string]: string } + methodParameters: string[] } -export interface IRouter { - readonly routes: Route[] +export interface IRouter< + TRoute extends Route = Route +> { + readonly routes: TRoute[] - getRouteData(request: Request): RouteData + getRouteData(request: RequestEvent): RouteData } diff --git a/src/routing/router.ts b/src/routing/router.ts index 4e4246c..3a858aa 100644 --- a/src/routing/router.ts +++ b/src/routing/router.ts @@ -1,12 +1,12 @@ import { MindlessError } from '../error/mindless.error' +import { RequestEvent } from '../request' import { Route } from './routes' import { Middleware } from '../middleware/middleware' import { Controller } from '../controller/controller' import { Request } from '../request' -import { IRouter } from './IRouter' +import { IRouter, RouteData, RouteMetadata } from './IRouter' -export class Router> - implements IRouter { +export class Router> implements IRouter { /** * Map that keeps a cache of the names of parameters each controller function requires * `key` is of the form - @@ -14,23 +14,7 @@ export class Router): { [key: string]: any } { - /** - * controller and middleware are constructors - * there should be no need for them - */ - const isUsefulKey = (key: string) => - typeof (route as any)[key] !== 'undefined' && key !== 'controller' && key !== 'middleware' - - return Object.keys(route) - .filter(isUsefulKey) - .reduce((metaData: any, key) => { - metaData[key] = (route as any)[key] - return metaData - }, {}) - } + constructor(protected _routes: TRoute[]) {} private static getParameters(func: Function) { const funcPieces = func.toString().match(/\(([^)]*)\)/) @@ -47,7 +31,7 @@ export class Router arg) // don't add undefined } - public get routes(): R[] { + public get routes(): TRoute[] { return this._routes } @@ -58,39 +42,46 @@ export class Router { + const [route, pathParameters] = this.getRequestedRoute(request) - request.RouteMetaData = Router.getRouteMetaData(route) + const metadata = this.getRouteMetaData(route) - const params = this.getMethodParameters(route) + const methodParameters = this.getMethodParameters(route) - return { route, params } + return { route, metadata, pathParameters, methodParameters } } - protected getRequestedRoute(request: Request): R { - const isRequestedRoute = (route: Route) => { - if (route.method !== request.method) { - return false - } - let params = route.url.match(request.path) - if (params) { - request.addMultiple(params) - return true + protected getRequestedRoute(request: RequestEvent): [TRoute, { [key: string]: string }] { + for (const route of this._routes) { + if (route.method === request.method) { + const params = route.url.match(request.path) + if (params) { + return [route, params] + } } - return false } - let route = this._routes.find(isRequestedRoute) + throw new MindlessError('Could not find requested route.') + } - if (route) { - return route - } + protected getRouteMetaData(route: Route): RouteMetadata { + /** + * controller and middleware are constructors + * there should be no need for them + */ + const isUsefulKey = (key: string) => + typeof (route as any)[key] !== 'undefined' && key !== 'controller' && key !== 'middleware' - throw new MindlessError('Could not find requested route.') + return Object.keys(route) + .filter(isUsefulKey) + .reduce((metaData: any, key) => { + metaData[key] = (route as any)[key] + return metaData + }, {}) } - protected getMethodParameters(route: R) { + protected getMethodParameters(route: TRoute) { const key = `${route.controller.name}-${route.function}` if (this.methodParameterCache[key] === undefined) { diff --git a/test/app/app.test.ts b/test/app/app.test.ts index a092e92..74d68d8 100644 --- a/test/app/app.test.ts +++ b/test/app/app.test.ts @@ -1,7 +1,7 @@ import * as TypeMoq from 'typemoq' +import { Dispatcher } from '../../src/app/dispatcher' import { CustomErrorHandler } from '../../src/error/custom-error-handler' import { GenericConstructor } from '../../src/interfaces' -import { Dispatcher } from '../../src/app' import { App, Controller, @@ -13,6 +13,9 @@ import { Response, RouteUrl } from '../../src/mindless' +import { RequestEvent } from '../../src/request' +import { DefaultBodyDeserializer } from '../../src/request/default-body-deserializer' +import { RouteData } from '../../src/routing/IRouter' describe('App', () => { const containerMock = TypeMoq.Mock.ofType() @@ -46,25 +49,46 @@ describe('App', () => { }) describe('App handle request', () => { - const requestMock = TypeMoq.Mock.ofType() + const eventMock = TypeMoq.Mock.ofType() const containerMock = TypeMoq.Mock.ofType() const routerMock = TypeMoq.Mock.ofType() const controllerConstructorMock = TypeMoq.Mock.ofType>() const middlewareConstructorMock = TypeMoq.Mock.ofType>() const dispatchMiddlewareMock = TypeMoq.Mock.ofInstance(Dispatcher.dispatchMiddleware) const dispatchControllerMock = TypeMoq.Mock.ofInstance(Dispatcher.dispatchController) + const errorHandlerMock = TypeMoq.Mock.ofType() + const bodyDeserializerMock = TypeMoq.Mock.ofType() beforeEach(() => { - requestMock.reset() + eventMock.reset() containerMock.reset() routerMock.reset() controllerConstructorMock.reset() middlewareConstructorMock.reset() dispatchMiddlewareMock.reset() dispatchControllerMock.reset() + errorHandlerMock.reset() + bodyDeserializerMock.reset() + }) + + afterEach(() => { + eventMock.verifyAll() + containerMock.verifyAll() + routerMock.verifyAll() + controllerConstructorMock.verifyAll() + middlewareConstructorMock.verifyAll() + dispatchMiddlewareMock.verifyAll() + dispatchControllerMock.verifyAll() + errorHandlerMock.verifyAll() + bodyDeserializerMock.verifyAll() }) test('successfully handle request', async () => { - const app = new App(containerMock.object, routerMock.object) + const app = new App( + containerMock.object, + routerMock.object, + errorHandlerMock.object, + bodyDeserializerMock.object + ) Dispatcher.dispatchMiddleware = dispatchMiddlewareMock.object Dispatcher.dispatchController = dispatchControllerMock.object @@ -80,32 +104,53 @@ describe('App handle request', () => { params: [] } + const routeData: RouteData = { + route: data.route, + metadata: { url: data.route.url, method: data.route.method, function: data.route.function }, + pathParameters: {}, + methodParameters: [] + } + routerMock - .setup(r => r.getRouteData(requestMock.object)) - .returns(() => data) + .setup(r => r.getRouteData(eventMock.object)) + .returns(() => routeData) + .verifiable(TypeMoq.Times.once()) + + bodyDeserializerMock + .setup(bd => bd.deserialize(eventMock.object, routeData.metadata)) .verifiable(TypeMoq.Times.once()) dispatchMiddlewareMock - .setup(m => m(containerMock.object, requestMock.object, data.route.middleware)) + .setup(m => + m(containerMock.object, TypeMoq.It.is(obj => obj instanceof Request), data.route.middleware) + ) .verifiable(TypeMoq.Times.once()) dispatchControllerMock - .setup(c => c(containerMock.object, requestMock.object, data.route, data.params)) + .setup(c => + c( + containerMock.object, + TypeMoq.It.is(obj => obj instanceof Request), + data.route, + data.params + ) + ) .returns(() => Promise.resolve(new Response())) .verifiable(TypeMoq.Times.once()) - const response = await app.handleRequest(requestMock.object) + const response = await app.handleRequest(eventMock.object) expect(response).toBeInstanceOf(Response) expect(response.statusCode).toBe(200) - - routerMock.verifyAll() - dispatchMiddlewareMock.verifyAll() - dispatchControllerMock.verifyAll() }) test('successfully handle request without middleware', async () => { - const app = new App(containerMock.object, routerMock.object) + const app = new App( + containerMock.object, + routerMock.object, + errorHandlerMock.object, + bodyDeserializerMock.object + ) Dispatcher.dispatchController = dispatchControllerMock.object @@ -119,27 +164,47 @@ describe('App handle request', () => { params: [] } + const routeData: RouteData = { + route: data.route, + metadata: { url: data.route.url, method: data.route.method, function: data.route.function }, + pathParameters: {}, + methodParameters: [] + } + routerMock - .setup(r => r.getRouteData(requestMock.object)) - .returns(() => data) + .setup(r => r.getRouteData(eventMock.object)) + .returns(() => routeData) + .verifiable(TypeMoq.Times.once()) + + bodyDeserializerMock + .setup(bd => bd.deserialize(eventMock.object, routeData.metadata)) .verifiable(TypeMoq.Times.once()) dispatchControllerMock - .setup(c => c(containerMock.object, requestMock.object, data.route, data.params)) + .setup(c => + c( + containerMock.object, + TypeMoq.It.is(obj => obj instanceof Request), + data.route, + data.params + ) + ) .returns(() => Promise.resolve(new Response())) .verifiable(TypeMoq.Times.once()) - const response = await app.handleRequest(requestMock.object) + const response = await app.handleRequest(eventMock.object) expect(response).toBeInstanceOf(Response) expect(response.statusCode).toBe(200) - - routerMock.verifyAll() - dispatchControllerMock.verifyAll() }) test('middleware rejects with response', async () => { - const app = new App(containerMock.object, routerMock.object) + const app = new App( + containerMock.object, + routerMock.object, + errorHandlerMock.object, + bodyDeserializerMock.object + ) Dispatcher.dispatchMiddleware = dispatchMiddlewareMock.object Dispatcher.dispatchController = dispatchControllerMock.object @@ -155,135 +220,140 @@ describe('App handle request', () => { params: [] } + const routeData: RouteData = { + route: data.route, + metadata: { url: data.route.url, method: data.route.method, function: data.route.function }, + pathParameters: {}, + methodParameters: [] + } + const errorMsg = 'error message' routerMock - .setup(r => r.getRouteData(requestMock.object)) - .returns(() => data) + .setup(r => r.getRouteData(eventMock.object)) + .returns(() => routeData) + .verifiable(TypeMoq.Times.once()) + + bodyDeserializerMock + .setup(bd => bd.deserialize(eventMock.object, routeData.metadata)) .verifiable(TypeMoq.Times.once()) dispatchMiddlewareMock - .setup(m => m(containerMock.object, requestMock.object, data.route.middleware)) + .setup(m => + m(containerMock.object, TypeMoq.It.is(obj => obj instanceof Request), data.route.middleware) + ) .returns(() => Promise.reject(new Response(399, { rejected: errorMsg }))) .verifiable(TypeMoq.Times.once()) dispatchControllerMock - .setup(c => c(containerMock.object, requestMock.object, data.route, data.params)) - .throws(new Error(errorMsg)) + .setup(c => c(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .verifiable(TypeMoq.Times.never()) - const response = await app.handleRequest(requestMock.object) + const response = await app.handleRequest(eventMock.object) expect(response).toBeInstanceOf(Response) expect(response.statusCode).toBe(399) expect(response.body.rejected).toEqual(errorMsg) - - routerMock.verifyAll() - dispatchMiddlewareMock.verifyAll() - dispatchControllerMock.verifyAll() }) - test('error with default error handler', async () => { - const app = new App(containerMock.object, routerMock.object) + test('handle error from getRouteData', async () => { + const app = new App( + containerMock.object, + routerMock.object, + errorHandlerMock.object, + bodyDeserializerMock.object + ) Dispatcher.dispatchMiddleware = dispatchMiddlewareMock.object Dispatcher.dispatchController = dispatchControllerMock.object - const data = { - route: { - url: new RouteUrl(''), - method: HttpMethods.GET, - function: 'test', - controller: controllerConstructorMock.object, - middleware: [middlewareConstructorMock.object] - }, - params: [] - } - - const errorMsg = 'error message' + const error = new Error('error message') + const expectedRes = new Response(399, { err: 'blah' }) routerMock - .setup(r => r.getRouteData(requestMock.object)) - .returns(() => data) + .setup(r => r.getRouteData(eventMock.object)) + .throws(error) .verifiable(TypeMoq.Times.once()) - dispatchMiddlewareMock - .setup(m => m(containerMock.object, requestMock.object, data.route.middleware)) + errorHandlerMock + .setup(handler => handler(error, eventMock.object)) + .returns(() => expectedRes) .verifiable(TypeMoq.Times.once()) dispatchControllerMock - .setup(c => c(containerMock.object, requestMock.object, data.route, data.params)) - .throws(new Error(errorMsg)) - .verifiable(TypeMoq.Times.once()) + .setup(c => c(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .verifiable(TypeMoq.Times.never()) - const response = await app.handleRequest(requestMock.object) + dispatchMiddlewareMock + .setup(m => m(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .verifiable(TypeMoq.Times.never()) - expect(response).toBeInstanceOf(Response) - expect(response.statusCode).toBe(500) - expect(response.body.message).toMatch(/An error occurred/) - expect(response.body.message).toMatch(/using default error handler/) + const actualResponse = await app.handleRequest(eventMock.object) - routerMock.verifyAll() - dispatchMiddlewareMock.verifyAll() - dispatchControllerMock.verifyAll() + expect(actualResponse).toBeInstanceOf(Response) + expect(actualResponse).toBe(expectedRes) }) - test('error with custom error handler', async () => { - const key = 'key' - const value = 'special request value' + test('handle error from dispatching controller', async () => { + const app = new App( + containerMock.object, + routerMock.object, + errorHandlerMock.object, + bodyDeserializerMock.object + ) - const customErrorHandler: CustomErrorHandler = (e: Error, request: Request) => { - return new Response(200, { message: `request value: ${request.get(key)}` }) - } - - const app = new App(containerMock.object, routerMock.object, customErrorHandler) - - Dispatcher.dispatchMiddleware = dispatchMiddlewareMock.object Dispatcher.dispatchController = dispatchControllerMock.object + const error = new Error('error message') + const expectedRes = new Response(399, { err: 'blah' }) + const data = { route: { url: new RouteUrl(''), method: HttpMethods.GET, function: 'test', controller: controllerConstructorMock.object, - middleware: [middlewareConstructorMock.object] + middleware: [] }, params: [] } - const errorMsg = 'error message' + const routeData: RouteData = { + route: data.route, + metadata: { url: data.route.url, method: data.route.method, function: data.route.function }, + pathParameters: {}, + methodParameters: [] + } - requestMock - .setup(r => r.get(key)) - .returns(() => value) + routerMock + .setup(r => r.getRouteData(eventMock.object)) + .returns(() => routeData) .verifiable(TypeMoq.Times.once()) - routerMock - .setup(r => r.getRouteData(requestMock.object)) - .returns(() => data) + bodyDeserializerMock + .setup(bd => bd.deserialize(eventMock.object, routeData.metadata)) .verifiable(TypeMoq.Times.once()) - dispatchMiddlewareMock - .setup(m => m(containerMock.object, requestMock.object, data.route.middleware)) + errorHandlerMock + .setup(handler => handler(error, TypeMoq.It.is(obj => obj instanceof Request))) + .returns(() => expectedRes) .verifiable(TypeMoq.Times.once()) dispatchControllerMock - .setup(c => c(containerMock.object, requestMock.object, data.route, data.params)) - .throws(new Error(errorMsg)) + .setup(c => + c( + containerMock.object, + TypeMoq.It.is(obj => obj instanceof Request), + data.route, + data.params + ) + ) + .returns(() => Promise.reject(error)) .verifiable(TypeMoq.Times.once()) - process.env.NODE_ENV = 'prod' - - const response = await app.handleRequest(requestMock.object) - expect(response).toBeInstanceOf(Response) - expect(response.statusCode).toBe(200) - expect(response.body.message).toMatch(/request value/) - expect(response.body.message).toMatch(new RegExp(value)) + const actualResponse = await app.handleRequest(eventMock.object) - requestMock.verifyAll() - routerMock.verifyAll() - dispatchMiddlewareMock.verifyAll() - dispatchControllerMock.verifyAll() + expect(actualResponse).toBeInstanceOf(Response) + expect(actualResponse).toBe(expectedRes) }) }) diff --git a/test/request/request.test.ts b/test/request/request.test.ts index 00efc82..c21af22 100644 --- a/test/request/request.test.ts +++ b/test/request/request.test.ts @@ -1,61 +1,54 @@ -import { Event, HttpMethods, Request } from '../../src/request' +import { RequestEvent, HttpMethods, Request } from '../../src/request' +import { RouteUrl } from '../../src/routing' +import { RouteMetadata } from '../../src/routing/IRouter' -import * as TypeMoq from 'typemoq' - -function getEvent(): Event { +function getEvent(path: string = '', method: HttpMethods = HttpMethods.GET): RequestEvent { return { + path, + method, headers: {}, - path: '', - pathParameters: {}, - requestContext: {}, - resource: '', - httpMethod: 'GET', queryStringParameters: {}, - stageVariables: {}, - body: '' + body: {} } } -describe('Test request constructor', () => { - // construct an event object - // no need to mock just a DTO essentially - // const eventMock: TypeMoq.IMock = TypeMoq.Mock.ofType(Event); - const localEvent: Event = getEvent() // default event with no data. +function getRouteMetadata(): RouteMetadata { + return { + function: '', + url: new RouteUrl(''), + method: HttpMethods.GET + } +} - test('empty event', () => { - let request = new Request(localEvent) - expect(request.path).toBe('') - expect(request.method).toBe(HttpMethods.GET) +describe('Test request constructor', () => { + test('method and path are properly exposed', () => { + const path = 'some-path' + const method = HttpMethods.POST + const request = new Request(getEvent(path, method), {}, getRouteMetadata()) + expect(request.path).toEqual(path) + expect(request.method).toEqual(method) }) - test('successfully parses json event body', () => { - let eventWithBody = localEvent - let body = { + test('request exposes body object', () => { + const event = getEvent() + event.body = { name: 'zach', number: 12345, things: ['a', 'b', 'c'] } - eventWithBody.body = JSON.stringify(body) - let request = new Request(eventWithBody) + const request = new Request(event, {}, getRouteMetadata()) - expect(request.get('name')).toBe('zach') - expect(request.get('number')).toBe(12345) - expect(request.get('things')).toEqual(['a', 'b', 'c']) + expect(request.body).toEqual(event.body) }) - // needed to not break Request.get() - test('defaults pathParameters, queryStringParameters and headers if null', () => { - let defaultEvent = localEvent - defaultEvent.pathParameters = null - defaultEvent.queryStringParameters = null - defaultEvent.headers = null + test('request exposes routeMetedata', () => { + const routeMetadata = getRouteMetadata() + routeMetadata.function = 'blah' - let request = new Request(defaultEvent) - expect(() => { - request.getOrFail('abc') - }).toThrow(/key not found/) - expect(request.get('abc')).toBeUndefined() + const request = new Request(getEvent(), {}, routeMetadata) + + expect(request.routeMetadata).toEqual(routeMetadata) }) }) @@ -142,7 +135,7 @@ describe('Test request header', () => { request.headerOrFail('abc') }).toThrow(/key not found/) }) - + test('headerOrFail retrieve header', () => { let event = getEvent() event.headers['test'] = 'val' @@ -150,16 +143,15 @@ describe('Test request header', () => { expect(request.headerOrFail('test')).toBe('val') }) - test('undefined header value', () => { let event = getEvent() let request = new Request(event) - + expect(() => { request.header('abc').toBeUndefined() - } + }) }) - + test('retrieve header value', () => { let event = getEvent() event.headers['test'] = 'val' @@ -168,7 +160,6 @@ describe('Test request header', () => { expect(request.header('test')).toBe('val') }) - }) describe('Test request add method', () => { diff --git a/tsconfig.json b/tsconfig.json index 215a7f1..cef4c33 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "declarationDir": "dist/types", + "strictPropertyInitialization": false, "outDir": "dist/lib", "typeRoots": [ "node_modules/@types" From 52710d59285ab24323f29a2cab940800aa89622a Mon Sep 17 00:00:00 2001 From: Zachary Rosenthal Date: Sun, 28 Apr 2019 17:04:06 -0400 Subject: [PATCH 2/5] test(Request and App): finsih updating request and app classes add functinality to support future features outlined in issue #73 re #86 --- src/request/request.ts | 15 +-- src/routing/router.ts | 7 +- test/app/dispatcher.test.ts | 87 +++++++-------- test/request/request.test.ts | 206 ++++++++++------------------------- test/response.test.ts | 17 ++- test/routing/router.test.ts | 150 +++++++++++-------------- 6 files changed, 187 insertions(+), 295 deletions(-) diff --git a/src/request/request.ts b/src/request/request.ts index 48567f2..2a4b642 100644 --- a/src/request/request.ts +++ b/src/request/request.ts @@ -1,15 +1,12 @@ import { MindlessError } from '../error/mindless.error' import { RouteMetadata } from '../routing/IRouter' import { HttpMethods } from './http-methods' -import { RequestEvent } from './request-event' export class Request { protected _pathParam: Readonly<{ [key: string]: string }> protected _queryParam: Readonly<{ [key: string]: string | string[] }> protected _header: Readonly<{ [key: string]: string | string[] }> - protected _context = new Map() - protected _routeMetadata: Readonly protected _method: Readonly @@ -42,11 +39,11 @@ export class Request { return this._routeMetadata } - public getPathParameter(key: string) { + public getPathParameter(key: string): string | undefined { return this._pathParam[key] } - public getPathParameterOrFail(key: string) { + public getPathParameterOrFail(key: string): string { const value = this.getPathParameter(key) if (value !== undefined) { @@ -56,11 +53,11 @@ export class Request { throw new MindlessError(`Invalid key: '${key}', key not found in path parameters`) } - public getQueryStringParameter(key: string) { + public getQueryStringParameter(key: string): string | string[] | undefined { return this._queryParam[key] } - public getQueryStringParameterOrFail(key: string) { + public getQueryStringParameterOrFail(key: string): string | string[] { const value = this.getQueryStringParameter(key) if (value !== undefined) { @@ -70,11 +67,11 @@ export class Request { throw new MindlessError(`Invalid key: '${key}', key not found in query string parameters`) } - public getHeader(key: string) { + public getHeader(key: string): string | string[] | undefined { return this._header[key] } - public getHeaderOrFaile(key: string) { + public getHeaderOrFail(key: string): string | string[] { const value = this.getHeader(key) if (value !== undefined) { diff --git a/src/routing/router.ts b/src/routing/router.ts index 3a858aa..39acd44 100644 --- a/src/routing/router.ts +++ b/src/routing/router.ts @@ -1,10 +1,9 @@ +import { Controller } from '../controller/controller' import { MindlessError } from '../error/mindless.error' -import { RequestEvent } from '../request' -import { Route } from './routes' import { Middleware } from '../middleware/middleware' -import { Controller } from '../controller/controller' -import { Request } from '../request' +import { Request, RequestEvent } from '../request' import { IRouter, RouteData, RouteMetadata } from './IRouter' +import { Route } from './routes' export class Router> implements IRouter { /** diff --git a/test/app/dispatcher.test.ts b/test/app/dispatcher.test.ts index b81cedb..0ea2c14 100644 --- a/test/app/dispatcher.test.ts +++ b/test/app/dispatcher.test.ts @@ -1,39 +1,38 @@ import * as TypeMoq from 'typemoq' -import { Dispatcher } from '../../src/app' +import { Dispatcher } from '../../src/app/dispatcher' import { MindlessError } from '../../src/error/mindless.error' import { GenericConstructor } from '../../src/interfaces' -import { MiddlewareHandle } from '../../src/middleware/middleware-handle' import { Controller, + Event, HttpMethods, IContainer, Middleware, Request, Response, - RouteUrl, - Event + RouteUrl } from '../../src/mindless' import { ActionMiddlewareMock } from '../mocks/action-middleware.mock' import { MiddlewareMock } from '../mocks/middleware.mock' describe('Dispatch middleware', () => { const containerMock = TypeMoq.Mock.ofType() + const requestMock = TypeMoq.Mock.ofType() beforeEach(() => { containerMock.reset() + requestMock.reset() }) test('no middleware', async () => { - const request = new Request({} as Event) - const middlewareList: GenericConstructor[] = [] containerMock.setup(c => c.resolve(TypeMoq.It.isAny())).verifiable(TypeMoq.Times.never()) const response = await Dispatcher.dispatchMiddleware( containerMock.object, - request, + requestMock.object, middlewareList ) @@ -43,7 +42,6 @@ describe('Dispatch middleware', () => { }) test('single middleware', async () => { - const request = new Request({} as Event) const middlewareList: GenericConstructor[] = [MiddlewareMock] const middlewareMock = new MiddlewareMock() @@ -54,7 +52,7 @@ describe('Dispatch middleware', () => { const response = await Dispatcher.dispatchMiddleware( containerMock.object, - request, + requestMock.object, middlewareList ) @@ -65,30 +63,24 @@ describe('Dispatch middleware', () => { }) test('multiple middleware are called in correct order', async () => { - const request = new Request({} as Event) - class AnotherMiddlewareMock extends MiddlewareMock {} - let order = 0 const getTag = () => (order++).toString() - const middlewareList: GenericConstructor[] = [MiddlewareMock, AnotherMiddlewareMock] + const middlewareList: GenericConstructor[] = [MiddlewareMock, MiddlewareMock] const middlewareMock = new MiddlewareMock(getTag) - const anotherMiddlewareMock = new AnotherMiddlewareMock(getTag) + const anotherMiddlewareMock = new MiddlewareMock(getTag) - containerMock - .setup(c => c.resolve(MiddlewareMock)) - .returns(() => middlewareMock) - .verifiable(TypeMoq.Times.once()) + containerMock.setup(c => c.resolve(MiddlewareMock)).returns(() => middlewareMock) containerMock - .setup(c => c.resolve(AnotherMiddlewareMock)) + .setup(c => c.resolve(MiddlewareMock)) .returns(() => anotherMiddlewareMock) - .verifiable(TypeMoq.Times.once()) + .verifiable(TypeMoq.Times.exactly(2)) const response = await Dispatcher.dispatchMiddleware( containerMock.object, - request, + requestMock.object, middlewareList ) @@ -102,33 +94,37 @@ describe('Dispatch middleware', () => { }) test('multiple middleware share same request', async () => { - const request = new Request({} as Event) - class AnotherMiddlewareMock extends ActionMiddlewareMock {} - - const middlewareList: GenericConstructor[] = [MiddlewareMock, AnotherMiddlewareMock] + const middlewareList: GenericConstructor[] = [ + ActionMiddlewareMock, + ActionMiddlewareMock + ] const key = 'key' const value = 'special value from middleware 1' - const middlewareMock = new ActionMiddlewareMock(req => req.add(key, value)) + const middlewareMock = new ActionMiddlewareMock(req => req.addContext(key, value)) let valueFromAnotherMiddlewareMock: string | undefined = undefined - const anotherMiddlewareMock = new AnotherMiddlewareMock( - req => (valueFromAnotherMiddlewareMock = req.get(key)) + const anotherMiddlewareMock = new ActionMiddlewareMock( + req => (valueFromAnotherMiddlewareMock = req.getContext(key)) ) - containerMock - .setup(c => c.resolve(MiddlewareMock)) - .returns(() => middlewareMock) - .verifiable(TypeMoq.Times.once()) + containerMock.setup(c => c.resolve(ActionMiddlewareMock)).returns(() => middlewareMock) containerMock - .setup(c => c.resolve(AnotherMiddlewareMock)) + .setup(c => c.resolve(ActionMiddlewareMock)) .returns(() => anotherMiddlewareMock) + .verifiable(TypeMoq.Times.exactly(2)) + + requestMock.setup(r => r.addContext(key, value)).verifiable(TypeMoq.Times.once()) + + requestMock + .setup(r => r.getContext(key)) + .returns(() => value) .verifiable(TypeMoq.Times.once()) const response = await Dispatcher.dispatchMiddleware( containerMock.object, - request, + requestMock.object, middlewareList ) @@ -136,9 +132,9 @@ describe('Dispatch middleware', () => { expect(middlewareMock.numberOfTimesHandleHasBeenCalled).toEqual(1) expect(anotherMiddlewareMock.numberOfTimesHandleHasBeenCalled).toEqual(1) expect(valueFromAnotherMiddlewareMock).toEqual(value) - expect(request.get(key)).toEqual(value) containerMock.verifyAll() + requestMock.verifyAll() }) }) @@ -197,7 +193,7 @@ describe('Dispatch controller', () => { function: 'test' } - const params = ['test', 'test2'] + const params = ['test1', 'test2'] containerMock .setup(c => c.resolve(controllerConstructorMock.object)) @@ -205,16 +201,16 @@ describe('Dispatch controller', () => { .verifiable(TypeMoq.Times.once()) requestMock - .setup(r => r.getOrFail('test')) - .returns(() => 1) + .setup(r => r.getPathParameter('test1')) + .returns(() => '1') .verifiable(TypeMoq.Times.once()) requestMock - .setup(r => r.getOrFail('test2')) - .returns(() => 2) + .setup(r => r.getQueryStringParameter('test2')) + .returns(() => '2') .verifiable(TypeMoq.Times.once()) controllerMock - .setup(m => (m as any).test(1, 2)) + .setup(c => (c as any).test('1', '2')) .returns(() => Promise.resolve(new Response())) .verifiable(TypeMoq.Times.once()) @@ -333,8 +329,13 @@ describe('Dispatch controller fails', () => { .verifiable(TypeMoq.Times.once()) requestMock - .setup(r => r.getOrFail('test')) - .throws(new Error()) + .setup(r => r.getPathParameter('test')) + .returns(() => undefined) + .verifiable(TypeMoq.Times.once()) + + requestMock + .setup(r => r.getQueryStringParameter('test')) + .returns(() => undefined) .verifiable(TypeMoq.Times.once()) try { diff --git a/test/request/request.test.ts b/test/request/request.test.ts index c21af22..335b3fa 100644 --- a/test/request/request.test.ts +++ b/test/request/request.test.ts @@ -1,17 +1,8 @@ -import { RequestEvent, HttpMethods, Request } from '../../src/request' +import { MindlessError } from '../../src/mindless' +import { HttpMethods, Request } from '../../src/request' import { RouteUrl } from '../../src/routing' import { RouteMetadata } from '../../src/routing/IRouter' -function getEvent(path: string = '', method: HttpMethods = HttpMethods.GET): RequestEvent { - return { - path, - method, - headers: {}, - queryStringParameters: {}, - body: {} - } -} - function getRouteMetadata(): RouteMetadata { return { function: '', @@ -21,186 +12,109 @@ function getRouteMetadata(): RouteMetadata { } describe('Test request constructor', () => { + const path = 'some-path' + test('method and path are properly exposed', () => { - const path = 'some-path' - const method = HttpMethods.POST - const request = new Request(getEvent(path, method), {}, getRouteMetadata()) + const request = new Request(path, {}, getRouteMetadata()) expect(request.path).toEqual(path) - expect(request.method).toEqual(method) + expect(request.method).toEqual(HttpMethods.GET) }) test('request exposes body object', () => { - const event = getEvent() - event.body = { + const body = { name: 'zach', number: 12345, things: ['a', 'b', 'c'] } - const request = new Request(event, {}, getRouteMetadata()) + const request = new Request(path, body, getRouteMetadata()) - expect(request.body).toEqual(event.body) + expect(request.body).toEqual(body) }) test('request exposes routeMetedata', () => { const routeMetadata = getRouteMetadata() routeMetadata.function = 'blah' - const request = new Request(getEvent(), {}, routeMetadata) + const request = new Request(path, {}, routeMetadata) expect(request.routeMetadata).toEqual(routeMetadata) }) }) -describe('Test request get method ', () => { - const localEvent = getEvent() - test('get added params', () => { - let defaultEvent = Object.assign({}, localEvent) - let request = new Request(defaultEvent) - request.add('param', 'abc') - - let actualRetrievedValue = request.get('param') - - expect(actualRetrievedValue).toBe('abc') - }) - - test('get query string parameters', () => { - let defaultEvent = getEvent() - defaultEvent.queryStringParameters['param'] = 'abc' - - let request = new Request(defaultEvent) - - let actualRetrievedValue = request.get('param') - - expect(actualRetrievedValue).toBe('abc') - }) - - test('get body parameters', () => { - let defaultEvent = getEvent() - - defaultEvent.body = JSON.stringify({ param: 'abc' }) - - let request = new Request(defaultEvent) - - let actualRetrievedValue = request.get('param') - - expect(actualRetrievedValue).toBe('abc') - }) - - test('getOrFail retrieve body parameters', () => { - let defaultEvent = getEvent() - - defaultEvent.body = JSON.stringify({ param: 'abc' }) +describe('Test request get data ', () => { + const key = 'param' + const val = 'abc' + const obj = { param: 'abc' } - let request = new Request(defaultEvent) + test('set and get context', () => { + const request = new Request('', {}, getRouteMetadata()) + request.addContext(key, val) + const actual = request.getContext(key) - let actualRetrievedValue = request.getOrFail('param') - - expect(actualRetrievedValue).toBe('abc') + expect(actual).toBe(val) }) - test('invalid key getOrFail', () => { - let defaultEvent = getEvent() - defaultEvent.pathParameters['test'] = 'abc' - defaultEvent.queryStringParameters['testb'] = 'abc' - defaultEvent.body = JSON.stringify({ testc: 'abc' }) - - let request = new Request(defaultEvent) + test('get path parameters', () => { + const request = new Request('', {}, getRouteMetadata(), obj) + const actual = request.getPathParameter(key) + const actualFromOrFail = request.getPathParameterOrFail(key) - expect(() => { - request.getOrFail('abc') - }).toThrow(/key not found/) + expect(actual).toBe(val) + expect(actualFromOrFail).toBe(val) }) - test('key not found returns undefined', () => { - let defaultEvent = getEvent() - defaultEvent.pathParameters['test'] = 'abc' - defaultEvent.queryStringParameters['testb'] = 'abc' - defaultEvent.body = JSON.stringify({ testc: 'abc' }) - - let request = new Request(defaultEvent) + test('get path param or fail', () => { + const request = new Request('', {}, getRouteMetadata()) - let retrievedValue = request.get('abc') - - expect(retrievedValue).toBeUndefined() - }) -}) - -describe('Test request header', () => { - test('invalid key', () => { - let event = getEvent() - let request = new Request(event) - - expect(() => { - request.headerOrFail('abc') - }).toThrow(/key not found/) - }) + try { + const actual = request.getPathParameterOrFail(key) + } catch (e) { + expect(e).toBeInstanceOf(MindlessError) + } - test('headerOrFail retrieve header', () => { - let event = getEvent() - event.headers['test'] = 'val' - let request = new Request(event) - expect(request.headerOrFail('test')).toBe('val') + expect.assertions(1) }) - test('undefined header value', () => { - let event = getEvent() - let request = new Request(event) + test('get query string parameters', () => { + const request = new Request('', {}, getRouteMetadata(), {}, obj) + const actual = request.getQueryStringParameter(key) + const actualFromOrFail = request.getQueryStringParameterOrFail(key) - expect(() => { - request.header('abc').toBeUndefined() - }) + expect(actual).toBe(val) + expect(actualFromOrFail).toBe(val) }) - test('retrieve header value', () => { - let event = getEvent() - event.headers['test'] = 'val' + test('get query string param or fail', () => { + const request = new Request('', {}, getRouteMetadata()) - let request = new Request(event) - - expect(request.header('test')).toBe('val') - }) -}) - -describe('Test request add method', () => { - let event = getEvent() - let request: Request + try { + const actual = request.getQueryStringParameterOrFail(key) + } catch (e) { + expect(e).toBeInstanceOf(MindlessError) + } - beforeEach(() => { - // reset request object - request = new Request(event) + expect.assertions(1) }) - test('Add new key,val pair', () => { - request.add('abc', 'val') - let retrievedVal = request.get('abc') + test('get header', () => { + const request = new Request('', {}, getRouteMetadata(), {}, {}, obj) + const actual = request.getHeader(key) + const actualFromFail = request.getHeaderOrFail(key) - expect(retrievedVal).toBe('val') + expect(actual).toBe(val) + expect(actualFromFail).toBe(val) }) - test('Add new key,val pair with already existing key', () => { - request.add('abc', 'val') + test('get header or fail', () => { + const request = new Request('', {}, getRouteMetadata()) - let addKeyAlreadyExists = () => { - request.add('abc', 'val2') + try { + const actual = request.getHeaderOrFail(key) + } catch (e) { + expect(e).toBeInstanceOf(MindlessError) } - expect(addKeyAlreadyExists).toThrow(/key 'abc' already exists/) - expect(request.get('abc')).toBe('val') - }) - - test('Add new key,val pair with already existing key and overwrite set to true', () => { - request.add('abc', 'val') - request.add('abc', 'val2', true) - - expect(request.get('abc')).toBe('val2') - }) - - test('Add multiple key,val pair', () => { - const map = { abc: 'val', def: 'lav' } - request.addMultiple(map) - - expect(request.get('abc')).toBe('val') - expect(request.get('def')).toBe('lav') + expect.assertions(1) }) }) diff --git a/test/response.test.ts b/test/response.test.ts index 5ae2953..e57bf1f 100644 --- a/test/response.test.ts +++ b/test/response.test.ts @@ -9,15 +9,14 @@ describe('Response class test', () => { expect(response.headers).toEqual({}) }) - test('aws integration respone', () => { - const body = { msg: 'hello world' } - const headers = { aHeader: 'i am a header' } - const response = new Response(200, body, headers) + test('provide values', () => { + const code = 837 + const body = { key: 'val' } + const header = { headerKey: 'headerval' } + const response = new Response(code, body, header) - const awsResponse = response.toAWSLambdaIntegrationResponse() - - expect(awsResponse.statusCode).toBe(200) - expect(awsResponse.body).toBe(JSON.stringify(body)) - expect(awsResponse.headers).toBe(headers) + expect(response.statusCode).toBe(code) + expect(response.body).toEqual(body) + expect(response.headers).toEqual(header) }) }) diff --git a/test/routing/router.test.ts b/test/routing/router.test.ts index d3fa670..70fd025 100644 --- a/test/routing/router.test.ts +++ b/test/routing/router.test.ts @@ -10,7 +10,7 @@ import { } from '../../src/mindless' import * as TypeMoq from 'typemoq' -import { GenericConstructor } from '../../src/interfaces' +import { RequestEvent } from '../../src/request' class TestController extends Controller { test(): Response { @@ -34,15 +34,17 @@ class TestController extends Controller { return new Response(200, res) } } +const eventMock = TypeMoq.Mock.ofType() -describe('Router getRequestRoute returns the correct route and parameters', () => { - let requestMock = TypeMoq.Mock.ofType() - - beforeEach(() => { - requestMock.reset() - }) +beforeEach(() => { + eventMock.reset() +}) +afterEach(() => { + eventMock.verifyAll() +}) - let routes: MindlessRoute[] = [ +describe('Router getRequestRoute returns the correct route and parameters', () => { + const routes: MindlessRoute[] = [ { url: new RouteUrl('/test'), method: HttpMethods.POST, @@ -59,23 +61,23 @@ describe('Router getRequestRoute returns the correct route and parameters', () = } ] - const router = new Router(routes) + const router = new Router(routes) test('Throws error when route group undefined (method mismatch)', () => { - requestMock.setup(c => c.path).returns(() => '/test') - requestMock.setup(c => c.method).returns(() => HttpMethods.GET) + eventMock.setup(ev => ev.path).returns(() => '/test') + eventMock.setup(ev => ev.method).returns(() => HttpMethods.GET) expect(() => { - router.getRouteData(requestMock.object) + router.getRouteData(eventMock.object) }).toThrow('Could not find requested route.') }) test('Throws error when route group undefined (path mismatch)', () => { - requestMock.setup(c => c.path).returns(() => '/blah') - requestMock.setup(c => c.method).returns(() => HttpMethods.POST) + eventMock.setup(ev => ev.path).returns(() => '/blah') + eventMock.setup(ev => ev.method).returns(() => HttpMethods.POST) expect(() => { - router.getRouteData(requestMock.object) + router.getRouteData(eventMock.object) }).toThrow('Could not find requested route.') }) @@ -86,35 +88,44 @@ describe('Router getRequestRoute returns the correct route and parameters', () = function: routes[0].function } - requestMock - .setup(c => c.path) + eventMock + .setup(ev => ev.path) .returns(() => '/test') .verifiable(TypeMoq.Times.once()) - requestMock - .setup(c => c.method) + eventMock + .setup(ev => ev.method) .returns(() => HttpMethods.POST) .verifiable(TypeMoq.Times.once()) - requestMock - .setup(r => (r.RouteMetaData = TypeMoq.It.isValue(expectedRouteMetadata))) - .verifiable(TypeMoq.Times.once()) - const data = router.getRouteData(requestMock.object) + const data = router.getRouteData(eventMock.object) expect(data.route).toEqual(routes[0]) - expect(data.params.length).toEqual(0) - - requestMock.verifyAll() + expect(data.methodParameters).toHaveLength(0) + expect(data.pathParameters).toEqual({}) + expect(data.metadata.url).toEqual(expectedRouteMetadata.url) + expect(data.metadata.method).toEqual(expectedRouteMetadata.method) + expect(data.metadata.function).toEqual(expectedRouteMetadata.function) }) test('Finds the correct route and returns the route object with parameters', () => { - requestMock.setup(c => c.path).returns(() => '/test') - requestMock.setup(c => c.method).returns(() => HttpMethods.PUT) + const expectedRouteMetadata = { + url: routes[1].url, + method: routes[1].method, + function: routes[1].function + } - const data = router.getRouteData(requestMock.object) + eventMock.setup(ev => ev.path).returns(() => '/test') + eventMock.setup(ev => ev.method).returns(() => HttpMethods.PUT) + + const data = router.getRouteData(eventMock.object) expect(data.route).toEqual(routes[1]) - expect(data.params.length).toEqual(1) - expect(data.params[0]).toEqual('val') + expect(data.methodParameters).toHaveLength(1) + expect(data.methodParameters[0]).toEqual('val') + expect(data.pathParameters).toEqual({}) + expect(data.metadata.url).toEqual(expectedRouteMetadata.url) + expect(data.metadata.method).toEqual(expectedRouteMetadata.method) + expect(data.metadata.function).toEqual(expectedRouteMetadata.function) }) test('get routes', () => { @@ -124,13 +135,7 @@ describe('Router getRequestRoute returns the correct route and parameters', () = }) describe('Router add all path parameters to the request', () => { - let requestMock = TypeMoq.Mock.ofType() - - beforeEach(() => { - requestMock.reset() - }) - - let routes: MindlessRoute[] = [ + const routes: MindlessRoute[] = [ { url: new RouteUrl('/test/:id/:name'), method: HttpMethods.POST, @@ -140,34 +145,24 @@ describe('Router add all path parameters to the request', () => { } ] - const router = new Router(routes) + const router = new Router(routes) test('Finds the correct route and returns the route object with parameters', () => { - requestMock.setup(c => c.path).returns(() => '/test/123/abc') - requestMock.setup(c => c.method).returns(() => HttpMethods.POST) + eventMock.setup(ev => ev.path).returns(() => '/test/123/abc') + eventMock.setup(ev => ev.method).returns(() => HttpMethods.POST) - const data = router.getRouteData(requestMock.object) + const data = router.getRouteData(eventMock.object) const expectedParams = { id: '123', name: 'abc' } - requestMock.verify( - r => r.addMultiple(TypeMoq.It.isObjectWith(expectedParams)), - TypeMoq.Times.once() - ) - expect(data.route).toEqual(routes[0]) + expect(data.pathParameters).toEqual(expectedParams) }) }) describe('Route function value is not a method name of the controller', () => { - let requestMock = TypeMoq.Mock.ofType() - - beforeEach(() => { - requestMock.reset() - }) - let routes: MindlessRoute[] = [ { url: new RouteUrl('/test'), @@ -178,25 +173,19 @@ describe('Route function value is not a method name of the controller', () => { } ] - const router = new Router(routes) + const router = new Router(routes) test('throws correct error', () => { - requestMock.setup(c => c.path).returns(() => '/test') - requestMock.setup(c => c.method).returns(() => HttpMethods.GET) + eventMock.setup(ev => ev.path).returns(() => '/test') + eventMock.setup(ev => ev.method).returns(() => HttpMethods.GET) - expect(() => router.getRouteData(requestMock.object)).toThrow( + expect(() => router.getRouteData(eventMock.object)).toThrow( "'blah' is not a method on the controller 'TestController'" ) }) }) describe('Cannot parse function', () => { - let requestMock = TypeMoq.Mock.ofType() - - beforeEach(() => { - requestMock.reset() - }) - let routes: MindlessRoute[] = [ { url: new RouteUrl('/test'), @@ -207,29 +196,24 @@ describe('Cannot parse function', () => { } ] - const router = new Router(routes) + const router = new Router(routes) test('throws correct error', () => { - requestMock - .setup(r => r.path) + eventMock + .setup(ev => ev.path) .returns(() => '/test') .verifiable(TypeMoq.Times.once()) - requestMock - .setup(r => r.method) + eventMock + .setup(ev => ev.method) .returns(() => HttpMethods.GET) .verifiable(TypeMoq.Times.once()) - expect(() => router.getRouteData(requestMock.object)).toThrow( - 'Route has invalid function' - ) - - requestMock.verifyAll() + expect(() => router.getRouteData(eventMock.object)).toThrow('Route has invalid function') }) }) describe('methodParameterCache works', () => { - const requestMock = TypeMoq.Mock.ofType() const functionMock = TypeMoq.Mock.ofType<() => number>() class MyController extends Controller { @@ -241,11 +225,10 @@ describe('methodParameterCache works', () => { MyController.prototype.test = functionMock.object beforeEach(() => { - requestMock.reset() functionMock.reset() }) - let routes: MindlessRoute[] = [ + const routes: MindlessRoute[] = [ { url: new RouteUrl('/test'), method: HttpMethods.GET, @@ -254,16 +237,16 @@ describe('methodParameterCache works', () => { function: 'test' } ] - const router = new Router(routes) + const router = new Router(routes) test('will not parse function if already parsed', () => { - requestMock - .setup(r => r.path) + eventMock + .setup(ev => ev.path) .returns(() => '/test') .verifiable(TypeMoq.Times.exactly(2)) - requestMock - .setup(r => r.method) + eventMock + .setup(ev => ev.method) .returns(() => HttpMethods.GET) .verifiable(TypeMoq.Times.exactly(2)) @@ -272,13 +255,12 @@ describe('methodParameterCache works', () => { .returns(() => '()') .verifiable(TypeMoq.Times.once()) - const data1 = router.getRouteData(requestMock.object) - const data2 = router.getRouteData(requestMock.object) + const data1 = router.getRouteData(eventMock.object) + const data2 = router.getRouteData(eventMock.object) expect(data1.route).toEqual(routes[0]) expect(data1).toEqual(data2) - requestMock.verifyAll() functionMock.verifyAll() }) }) From abe34824f2524eb2862f831af22625af780b6c23 Mon Sep 17 00:00:00 2001 From: zRosenthal Date: Fri, 26 Jul 2019 10:00:02 -0400 Subject: [PATCH 3/5] refactor(request objects): change params, query string, and headers to be es6 maps BREAKING CHANGE: must now pass headers and query string through as es6 maps --- src/request/request-event.ts | 4 ++-- src/request/request.ts | 42 ++++++++++++++++-------------------- src/routing/IRouter.ts | 2 +- src/routing/router.ts | 4 ++-- test/request/request.test.ts | 25 +++++++++++++++++---- test/routing/router.test.ts | 9 +++----- tsconfig.json | 2 +- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/request/request-event.ts b/src/request/request-event.ts index e0f4382..7227a80 100644 --- a/src/request/request-event.ts +++ b/src/request/request-event.ts @@ -2,8 +2,8 @@ import { HttpMethods } from './http-methods' export interface RequestEvent { body: TBody - queryStringParameters: { [key: string]: string | string[] } - headers: { [key: string]: string | string[] } + queryStringParameters: ReadonlyMap + headers: ReadonlyMap path: string method: HttpMethods diff --git a/src/request/request.ts b/src/request/request.ts index 2a4b642..2a57052 100644 --- a/src/request/request.ts +++ b/src/request/request.ts @@ -3,9 +3,9 @@ import { RouteMetadata } from '../routing/IRouter' import { HttpMethods } from './http-methods' export class Request { - protected _pathParam: Readonly<{ [key: string]: string }> - protected _queryParam: Readonly<{ [key: string]: string | string[] }> - protected _header: Readonly<{ [key: string]: string | string[] }> + protected _pathParam: ReadonlyMap + protected _queryParam: ReadonlyMap + protected _header: ReadonlyMap protected _context = new Map() protected _routeMetadata: Readonly protected _method: Readonly @@ -14,14 +14,14 @@ export class Request { protected _path: Readonly, protected _body: Readonly, routeMetadata: RouteMetadata, - pathParameters: { [keys: string]: string } = {}, - queryStringParameters: { [key: string]: string | string[] } = {}, - headers: { [key: string]: string | string[] } = {} + pathParameters: ReadonlyMap = new Map(), // { [keys: string]: string } = {}, + queryStringParameters: ReadonlyMap = new Map(), // { [key: string]: string | string[] } = {}, + headers: ReadonlyMap = new Map() // { [key: string]: string | string[] } = {} ) { this._method = routeMetadata.method - this._pathParam = Object.freeze(pathParameters) - this._queryParam = Object.freeze(queryStringParameters) - this._header = Object.freeze(headers) + this._pathParam = pathParameters + this._queryParam = queryStringParameters + this._header = headers this._routeMetadata = Object.freeze(routeMetadata) } @@ -40,42 +40,36 @@ export class Request { } public getPathParameter(key: string): string | undefined { - return this._pathParam[key] + return this._pathParam.get(key) } public getPathParameterOrFail(key: string): string { - const value = this.getPathParameter(key) - - if (value !== undefined) { - return value + if (this._pathParam.has(key)) { + return this._pathParam.get(key)! } throw new MindlessError(`Invalid key: '${key}', key not found in path parameters`) } public getQueryStringParameter(key: string): string | string[] | undefined { - return this._queryParam[key] + return this._queryParam.get(key) } public getQueryStringParameterOrFail(key: string): string | string[] { - const value = this.getQueryStringParameter(key) - - if (value !== undefined) { - return value + if (this._queryParam.has(key)) { + return this._queryParam.get(key)! } throw new MindlessError(`Invalid key: '${key}', key not found in query string parameters`) } public getHeader(key: string): string | string[] | undefined { - return this._header[key] + return this._header.get(key) } public getHeaderOrFail(key: string): string | string[] { - const value = this.getHeader(key) - - if (value !== undefined) { - return value + if (this._header.has(key)) { + return this._header.get(key)! } throw new MindlessError(`Invalid key: '${key}', key not found in headers`) diff --git a/src/routing/IRouter.ts b/src/routing/IRouter.ts index 0857e8f..687abb9 100644 --- a/src/routing/IRouter.ts +++ b/src/routing/IRouter.ts @@ -14,7 +14,7 @@ export interface RouteData< > { route: TRoute metadata: RouteMetadata - pathParameters: { [key: string]: string } + pathParameters: ReadonlyMap methodParameters: string[] } diff --git a/src/routing/router.ts b/src/routing/router.ts index 39acd44..1395f10 100644 --- a/src/routing/router.ts +++ b/src/routing/router.ts @@ -51,12 +51,12 @@ export class Router> implements IRo return { route, metadata, pathParameters, methodParameters } } - protected getRequestedRoute(request: RequestEvent): [TRoute, { [key: string]: string }] { + protected getRequestedRoute(request: RequestEvent): [TRoute, ReadonlyMap] { for (const route of this._routes) { if (route.method === request.method) { const params = route.url.match(request.path) if (params) { - return [route, params] + return [route, new Map(Object.entries(params))] } } } diff --git a/test/request/request.test.ts b/test/request/request.test.ts index 335b3fa..c07768c 100644 --- a/test/request/request.test.ts +++ b/test/request/request.test.ts @@ -2,6 +2,7 @@ import { MindlessError } from '../../src/mindless' import { HttpMethods, Request } from '../../src/request' import { RouteUrl } from '../../src/routing' import { RouteMetadata } from '../../src/routing/IRouter' +import { escapeRegExp } from 'tslint/lib/utils' function getRouteMetadata(): RouteMetadata { return { @@ -32,6 +33,22 @@ describe('Test request constructor', () => { expect(request.body).toEqual(body) }) + test('request exposes body object of class instance', () => { + class TestBody { + constructor(public data: string) {} + getGreeting(name: string): string { + return `Hello ${name}` + } + } + const body = new TestBody('some data') + + const request = new Request(path, body, getRouteMetadata()) + + expect(request.body).toBeInstanceOf(TestBody) + expect(request.body.data).toEqual('some data') + expect(request.body.getGreeting('bob')).toEqual('Hello bob') + }) + test('request exposes routeMetedata', () => { const routeMetadata = getRouteMetadata() routeMetadata.function = 'blah' @@ -45,7 +62,7 @@ describe('Test request constructor', () => { describe('Test request get data ', () => { const key = 'param' const val = 'abc' - const obj = { param: 'abc' } + const obj = new Map([['param', 'abc']]) test('set and get context', () => { const request = new Request('', {}, getRouteMetadata()) @@ -77,7 +94,7 @@ describe('Test request get data ', () => { }) test('get query string parameters', () => { - const request = new Request('', {}, getRouteMetadata(), {}, obj) + const request = new Request('', {}, getRouteMetadata(), new Map(), obj) const actual = request.getQueryStringParameter(key) const actualFromOrFail = request.getQueryStringParameterOrFail(key) @@ -86,7 +103,7 @@ describe('Test request get data ', () => { }) test('get query string param or fail', () => { - const request = new Request('', {}, getRouteMetadata()) + const request = new Request('', new Map(), getRouteMetadata()) try { const actual = request.getQueryStringParameterOrFail(key) @@ -98,7 +115,7 @@ describe('Test request get data ', () => { }) test('get header', () => { - const request = new Request('', {}, getRouteMetadata(), {}, {}, obj) + const request = new Request('', {}, getRouteMetadata(), new Map(), new Map(), obj) const actual = request.getHeader(key) const actualFromFail = request.getHeaderOrFail(key) diff --git a/test/routing/router.test.ts b/test/routing/router.test.ts index 70fd025..81399a7 100644 --- a/test/routing/router.test.ts +++ b/test/routing/router.test.ts @@ -101,7 +101,7 @@ describe('Router getRequestRoute returns the correct route and parameters', () = expect(data.route).toEqual(routes[0]) expect(data.methodParameters).toHaveLength(0) - expect(data.pathParameters).toEqual({}) + expect(data.pathParameters).toEqual(new Map()) expect(data.metadata.url).toEqual(expectedRouteMetadata.url) expect(data.metadata.method).toEqual(expectedRouteMetadata.method) expect(data.metadata.function).toEqual(expectedRouteMetadata.function) @@ -122,7 +122,7 @@ describe('Router getRequestRoute returns the correct route and parameters', () = expect(data.route).toEqual(routes[1]) expect(data.methodParameters).toHaveLength(1) expect(data.methodParameters[0]).toEqual('val') - expect(data.pathParameters).toEqual({}) + expect(data.pathParameters).toEqual(new Map()) expect(data.metadata.url).toEqual(expectedRouteMetadata.url) expect(data.metadata.method).toEqual(expectedRouteMetadata.method) expect(data.metadata.function).toEqual(expectedRouteMetadata.function) @@ -152,10 +152,7 @@ describe('Router add all path parameters to the request', () => { eventMock.setup(ev => ev.method).returns(() => HttpMethods.POST) const data = router.getRouteData(eventMock.object) - const expectedParams = { - id: '123', - name: 'abc' - } + const expectedParams = new Map([['id', '123'], ['name', 'abc']]) expect(data.route).toEqual(routes[0]) expect(data.pathParameters).toEqual(expectedParams) diff --git a/tsconfig.json b/tsconfig.json index cef4c33..869597b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "declarationDir": "dist/types", - "strictPropertyInitialization": false, + "strictPropertyInitialization": true, "outDir": "dist/lib", "typeRoots": [ "node_modules/@types" From ccc889c5a9b5819f615aeb2e6a79e81607092eed Mon Sep 17 00:00:00 2001 From: zRosenthal Date: Fri, 26 Jul 2019 10:14:32 -0400 Subject: [PATCH 4/5] build(typescript version bump): updated to latest stable typescript version 3.5.x --- package-lock.json | 191 +++++++++++++++++++++++----------------------- package.json | 8 +- src/app/IApp.ts | 2 +- src/mindless.ts | 6 +- tsconfig.json | 1 + 5 files changed, 101 insertions(+), 107 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68527dd..7d3018a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -342,41 +342,44 @@ "dev": true }, "@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==", "dev": true }, "@types/fs-extra": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.1.tgz", - "integrity": "sha512-h3wnflb+jMTipvbbZnClgA2BexrT4w0GcfoCz5qyxd0IRsbqhLSyesM6mqZTAnhbVmhyTm5tuxfRu9R+8l+lGw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.1.0.tgz", + "integrity": "sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==", "dev": true, "requires": { "@types/node": "9.6.24" } }, "@types/glob": { - "version": "5.0.35", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.35.tgz", - "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "dev": true, "requires": { - "@types/events": "1.2.0", + "@types/events": "3.0.0", "@types/minimatch": "3.0.3", "@types/node": "9.6.24" } }, "@types/handlebars": { - "version": "4.0.36", - "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.36.tgz", - "integrity": "sha512-LjNiTX7TY7wtuC6y3QwC93hKMuqYhgV9A1uXBKNvZtVC8ZvyWAjZkJ5BvT0K7RKqORRYRLMrqCxpw5RgS+MdrQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-gq9YweFKNNB1uFK71eRqsd4niVkXrxHugqWFQkeLRJvGjnxsLr16bYtcsG4tOFwmYi0Bax+wCkbf1reUfdl4kA==", + "dev": true, + "requires": { + "handlebars": "4.0.11" + } }, "@types/highlight.js": { - "version": "9.12.2", - "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.2.tgz", - "integrity": "sha512-y5x0XD/WXDaGSyiTaTcKS4FurULJtSiYbGTeQd0m2LYZGBcZZ/7fM6t5H/DzeUF+kv8y6UfmF6yJABQsHcp9VQ==", + "version": "9.12.3", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.3.tgz", + "integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==", "dev": true }, "@types/jest": { @@ -392,9 +395,9 @@ "dev": true }, "@types/marked": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz", - "integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.4.2.tgz", + "integrity": "sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==", "dev": true }, "@types/minimatch": { @@ -416,25 +419,15 @@ "dev": true }, "@types/shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha512-M2giRw93PxKS7YjU6GZjtdV9HASdB7TWqizBXe4Ju7AqbKlWvTr0gNO92XH56D/gMxqD/jNHLNfC5hA34yGqrQ==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-bZgjwIWu9gHCjirKJoOlLzGi5N0QgZ5t7EXEuoqyWCHTuSddURXo3FOBYDyRPNOWzZ6NbkLvZnVkn483Y/tvcQ==", "dev": true, "requires": { - "@types/glob": "5.0.35", + "@types/glob": "7.1.1", "@types/node": "9.6.24" } }, - "JSONStream": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz", - "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "abab": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", @@ -2568,8 +2561,8 @@ "integrity": "sha512-GWh71U26BLWgMykCp+VghZ4s64wVbtseECcKQ/PvcPZR2cUnz+FUc2J9KjxNl7/ZbCxST8R03c9fc+Vi0umS9Q==", "dev": true, "requires": { - "JSONStream": "1.3.3", "is-text-path": "1.0.1", + "JSONStream": "1.3.3", "lodash": "4.17.5", "meow": "4.0.1", "split2": "2.2.0", @@ -4399,23 +4392,23 @@ "dev": true, "optional": true }, - "string-width": { - "version": "1.0.2", + "string_decoder": { + "version": "1.1.1", "bundled": true, "dev": true, + "optional": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "safe-buffer": "5.1.1" } }, - "string_decoder": { - "version": "1.1.1", + "string-width": { + "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { - "safe-buffer": "5.1.1" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -4875,9 +4868,9 @@ } }, "highlight.js": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz", - "integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4=", + "version": "9.15.8", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.8.tgz", + "integrity": "sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA==", "dev": true }, "home-or-tmp": { @@ -6520,6 +6513,16 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, + "JSONStream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz", + "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8618,9 +8621,9 @@ "dev": true }, "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "prompt": { @@ -10503,6 +10506,15 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, "string-argv": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz", @@ -10547,15 +10559,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, "stringify-object": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.2.tgz", @@ -11570,40 +11573,34 @@ "dev": true }, "typedoc": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.11.1.tgz", - "integrity": "sha512-jdNIoHm5wkZqxQTe/g9AQ3LKnZyrzHXqu6A/c9GUOeJyBWLxNr7/Dm3rwFvLksuxRNwTvY/0HRDU9sJTa9WQSg==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.14.2.tgz", + "integrity": "sha512-aEbgJXV8/KqaVhcedT7xG6d2r+mOvB5ep3eIz1KuB5sc4fDYXcepEEMdU7XSqLFO5hVPu0nllHi1QxX2h/QlpQ==", "dev": true, "requires": { - "@types/fs-extra": "5.0.1", - "@types/handlebars": "4.0.36", - "@types/highlight.js": "9.12.2", - "@types/lodash": "4.14.104", - "@types/marked": "0.3.0", + "@types/fs-extra": "5.1.0", + "@types/handlebars": "4.1.0", + "@types/highlight.js": "9.12.3", + "@types/lodash": "4.14.115", + "@types/marked": "0.4.2", "@types/minimatch": "3.0.3", - "@types/shelljs": "0.7.8", - "fs-extra": "5.0.0", + "@types/shelljs": "0.8.5", + "fs-extra": "7.0.1", "handlebars": "4.0.11", - "highlight.js": "9.12.0", - "lodash": "4.17.5", - "marked": "0.3.19", + "highlight.js": "9.15.8", + "lodash": "4.17.15", + "marked": "0.4.0", "minimatch": "3.0.4", - "progress": "2.0.0", - "shelljs": "0.8.2", + "progress": "2.0.3", + "shelljs": "0.8.3", "typedoc-default-themes": "0.5.0", - "typescript": "2.7.2" + "typescript": "3.2.4" }, "dependencies": { - "@types/lodash": { - "version": "4.14.104", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.104.tgz", - "integrity": "sha512-ufQcVg4daO8xQ5kopxRHanqFdL4AI7ondQkV+2f+7mz3gvp0LkBx2zBRC6hfs3T87mzQFmf5Fck7Fi145Ul6NQ==", - "dev": true - }, "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { "graceful-fs": "4.1.11", @@ -11620,16 +11617,16 @@ "graceful-fs": "4.1.11" } }, - "marked": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "shelljs": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", - "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", "dev": true, "requires": { "glob": "7.1.1", @@ -11638,9 +11635,9 @@ } }, "typescript": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", - "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz", + "integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==", "dev": true } } @@ -11663,9 +11660,9 @@ } }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index f235794..83cc3dd 100644 --- a/package.json +++ b/package.json @@ -99,16 +99,16 @@ "rollup-plugin-sourcemaps": "^0.4.2", "rollup-plugin-typescript2": "^0.11.1", "semantic-release": "^15.4.1", + "travis-deploy-once": "^5.0.0", "ts-jest": "^22.0.0", "ts-node": "^6.0.0", "tslint": "^5.8.0", "tslint-config-prettier": "^1.1.0", "tslint-config-standard": "^7.0.0", - "typedoc": "^0.11.0", + "typedoc": "^0.14.2", "typemoq": "^2.1.0", - "typescript": "^2.6.2", - "validate-commit-msg": "^2.12.2", - "travis-deploy-once": "^5.0.0" + "typescript": "^3.5.3", + "validate-commit-msg": "^2.12.2" }, "dependencies": { "route-parser": "0.0.5" diff --git a/src/app/IApp.ts b/src/app/IApp.ts index 9989e2a..61736f8 100644 --- a/src/app/IApp.ts +++ b/src/app/IApp.ts @@ -1,6 +1,6 @@ import { GenericConstructor } from '../interfaces' -import { RequestEvent } from '../request/request' import { Response } from '../response' +import { RequestEvent } from '../request' export interface IApp { resolve(constructor: GenericConstructor): T diff --git a/src/mindless.ts b/src/mindless.ts index 3d8a85d..b16d8cd 100644 --- a/src/mindless.ts +++ b/src/mindless.ts @@ -1,12 +1,8 @@ -// Import here Polyfills if needed. Recommended core-js (npm i -D core-js) -// import "core-js/fn/array.find" - export { MindlessError } from './error/mindless.error' export { CustomErrorHandler } from './error/custom-error-handler' export { App, IContainer, IApp } from './app' export { Router, Route, MindlessRoute, RouteUrl, IRouteUrl, IRouter } from './routing' -export { Context } from './interfaces' -export { Request, Event, HttpMethods } from './request' +export { Request, HttpMethods, RequestEvent } from './request' export { Response } from './response' export { Middleware } from './middleware/middleware' export { Controller } from './controller/controller' diff --git a/tsconfig.json b/tsconfig.json index 869597b..578acb8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "emitDecoratorMetadata": true, "declarationDir": "dist/types", "strictPropertyInitialization": true, + "strictNullChecks": true, "outDir": "dist/lib", "typeRoots": [ "node_modules/@types" From 1f891872a1dc7d0ce3642b94501a2adca8833afd Mon Sep 17 00:00:00 2001 From: zRosenthal Date: Fri, 26 Jul 2019 10:53:22 -0400 Subject: [PATCH 5/5] docs(rejadme): --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f27e7e4..8c2c87c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ [![Coverage Status](https://coveralls.io/repos/github/SpartanLabs/mindless/badge.svg?branch=master)](https://coveralls.io/github/SpartanLabs/mindless?branch=master) # mindless + +This documentation is out of date. + ### A Library for creating APIs with TypeScript. Mindless allows developers to write typical controller-styled apis with models using TypeScript. A great use of the mindless framework is with applications built using the [serverless framework](https://serverless.com/). In a typical serverless application, each route goes to its own function. Using mindless allows the developer to flip the script on this paradigm. Using the lightweight routing mechanism, developers can use routes to point to controllers based on path. Mindless also enables parameter injection and general dependency injection in controllers. Mindless will also have extensions such as permissions and data access that will further empower developers.