express-router-api
Advanced tools
Comparing version 1.2.2 to 1.2.3
@@ -45,2 +45,6 @@ "use strict"; | ||
const promiseProps = (obj) => { | ||
const keys = Object.keys(obj); | ||
if (!keys.length) { | ||
return rxjs_1.of(obj); | ||
} | ||
return rxjs_1.forkJoin(Object.keys(obj).map(key => { | ||
@@ -92,3 +96,5 @@ return resolve(obj[key]).pipe(operators_1.map(val => { | ||
}; | ||
const isPlainObject = (obj) => (typeof obj === 'object' && !(obj instanceof ApiResponse) && !(obj instanceof ApiError)); | ||
const isPlainObject = (obj) => { | ||
return (typeof obj === 'object' && !(obj instanceof ApiResponse) && !(obj instanceof ApiError)) && !(obj instanceof Array); | ||
}; | ||
const subscription = rxjs_1.defer(() => resolve(origHandler(req, res, next))) | ||
@@ -95,0 +101,0 @@ .pipe(operators_1.switchMap((obj) => { |
{ | ||
"name": "express-router-api", | ||
"version": "1.2.2", | ||
"version": "1.2.3", | ||
"description": "Express router that lets you construct your API entirely on promises", | ||
@@ -25,2 +25,3 @@ "main": "dist/index.js", | ||
"request-promise": "^4.2.2", | ||
"tslint": "^5.11.0", | ||
"typescript": "^3.1.3" | ||
@@ -27,0 +28,0 @@ }, |
@@ -1,5 +0,5 @@ | ||
import { Router, Request, Response, NextFunction, RequestParamHandler, RequestHandler, RouterOptions } from 'express'; | ||
import { NextFunction, Request, RequestHandler, RequestParamHandler, Response, Router, RouterOptions } from 'express'; | ||
import { PathParams, RequestHandlerParams } from 'express-serve-static-core'; | ||
import { Observable, isObservable, from, identity, defer, of, throwError, forkJoin } from 'rxjs'; | ||
import { switchMap, catchError, map } from 'rxjs/operators'; | ||
import { defer, forkJoin, from, identity, isObservable, Observable, of, throwError } from 'rxjs'; | ||
import { catchError, map, switchMap } from 'rxjs/operators'; | ||
@@ -35,3 +35,3 @@ const isPromise = (val: any): val is Promise<any> => (val && typeof val.then === 'function'); | ||
constructor(public apiResult: SimpleApiResult, public code: number = 200) {} | ||
}; | ||
} | ||
@@ -49,3 +49,3 @@ type ApiResult = SimpleApiResult | ApiResponse; | ||
internalServerError?: string; | ||
}; | ||
} | ||
@@ -62,8 +62,12 @@ function resolve<T>(value: T | Promise<T> | Observable<T>): Observable<T> { | ||
const promiseProps = (obj:{ [key: string]: string; }) => { | ||
return forkJoin(Object.keys(obj).map(key => { | ||
return resolve(obj[key]).pipe(map(val => { | ||
const promiseProps = (obj: { [key: string]: string; }) => { | ||
const keys = Object.keys(obj); | ||
if (!keys.length) { | ||
return of(obj); | ||
} | ||
return forkJoin(Object.keys(obj).map((key) => { | ||
return resolve(obj[key]).pipe(map((val) => { | ||
return { [key]: val }; | ||
})); | ||
})).pipe(map(results => Object.assign({}, ...results))); | ||
})).pipe(map((results) => Object.assign({}, ...results))); | ||
}; | ||
@@ -76,3 +80,3 @@ | ||
if(typeof apiResponse.apiResult === 'undefined') { | ||
if (typeof apiResponse.apiResult === 'undefined') { | ||
return false; | ||
@@ -85,11 +89,9 @@ } | ||
res.status(apiResponse.code); | ||
if(typeof apiResponse.apiResult === 'object') { | ||
if (typeof apiResponse.apiResult === 'object') { | ||
res.json(apiResponse.apiResult); | ||
return true; | ||
} | ||
else if(typeof apiResponse.apiResult === 'string') { | ||
} else if (typeof apiResponse.apiResult === 'string') { | ||
res.send(apiResponse.apiResult); | ||
return true; | ||
} | ||
else { | ||
} else { | ||
res.end(); | ||
@@ -100,3 +102,5 @@ return true; | ||
function toMiddleware(this: ExpressApiRouter, origHandler: RequestHandler, options: ApiRouterOptions = {}, callNext: boolean) { | ||
function toMiddleware(this: ExpressApiRouter, | ||
origHandler: RequestHandler, | ||
options: ApiRouterOptions = {}, callNext: boolean) { | ||
const internalServerError = options.internalServerError || {error: 'Internal server error'}; | ||
@@ -106,6 +110,6 @@ | ||
return defer(() => resolve(this.errorFormatter(err, req, res))) | ||
.pipe(map(formatted => formatted ? new ApiResponse(formatted) | ||
: new ApiResponse(err.message, err.statusCode || 500) | ||
.pipe(map((formatted) => formatted ? new ApiResponse(formatted) | ||
: new ApiResponse(err.message, err.statusCode || 500), | ||
)); | ||
} | ||
}; | ||
@@ -118,3 +122,3 @@ return (req: Request, res: Response, next: NextFunction) => { | ||
return defer(() => resolve(this.errorFormatter(err, req, res))) | ||
.pipe(map(formatted => new ApiResponse(formatted, 500))); | ||
.pipe(map((formatted) => new ApiResponse(formatted, 500))); | ||
} | ||
@@ -124,3 +128,8 @@ return of(undefined); | ||
const isPlainObject = (obj: ApiResult) => (typeof obj === 'object' && !(obj instanceof ApiResponse) && !(obj instanceof ApiError)); | ||
const isPlainObject = (obj: ApiResult) => { | ||
return (typeof obj === 'object' && | ||
!(obj instanceof ApiResponse) && | ||
!(obj instanceof ApiError)) && | ||
!(obj instanceof Array); | ||
}; | ||
const subscription = defer(() => resolve(origHandler(req, res, next))) | ||
@@ -132,7 +141,6 @@ .pipe( | ||
this.successFormatter ? formatterOperator : identity, | ||
switchMap(data => { | ||
switchMap((data) => { | ||
if (data instanceof ApiError) { | ||
return processApiError(data, req, res); | ||
} | ||
else if (!(data instanceof ApiResponse)) { | ||
} else if (!(data instanceof ApiResponse)) { | ||
return of(new ApiResponse(data, 200)); | ||
@@ -145,8 +153,8 @@ } | ||
res.emit('expressApiRouterError', err); | ||
if(!options.silenceExpressApiRouterError) { | ||
if (!options.silenceExpressApiRouterError) { | ||
// tslint:disable-next-line:no-console | ||
console.error(err.stack); | ||
} | ||
return of(undefined); | ||
} | ||
else if (err instanceof ApiError) { | ||
} else if (err instanceof ApiError) { | ||
return processApiError(err, req, res); | ||
@@ -158,3 +166,3 @@ } | ||
catchError((err: Error) => { | ||
return resolve(formatError(err)).pipe(map(jsonError => { | ||
return resolve(formatError(err)).pipe(map((jsonError) => { | ||
return new ApiResponse(jsonError || internalServerError, 500); | ||
@@ -174,3 +182,3 @@ })); | ||
}); | ||
req.on('close', () => subscription.unsubscribe()) | ||
req.on('close', () => subscription.unsubscribe()); | ||
}; | ||
@@ -181,8 +189,8 @@ } | ||
'options' | 'trace' | 'copy' | 'lock' |'mkcol' | 'move' |'purge' | | ||
'propfind' | 'proppatch' | 'unlock' | 'report' | 'mkactivity' | | ||
'propfind' | 'proppatch' | 'unlock' | 'report' | 'mkactivity' | | ||
'checkout' | 'merge' | 'm-search' | 'notify' | 'subscribe' | | ||
'unsubscribe' | 'patch' | 'search' | 'connect'; | ||
const methods: MethodName[] = [ 'get', 'post', 'put', 'head', 'delete', | ||
'options', 'trace', 'copy', 'lock','mkcol', 'move','purge', | ||
'propfind', 'proppatch', 'unlock', 'report', 'mkactivity', | ||
'options', 'trace', 'copy', 'lock', 'mkcol', 'move', 'purge', | ||
'propfind', 'proppatch', 'unlock', 'report', 'mkactivity', | ||
'checkout', 'merge', 'm-search', 'notify', 'subscribe', | ||
@@ -192,4 +200,4 @@ 'unsubscribe', 'patch', 'search', 'connect' ]; | ||
export interface ExpressApiRouter extends Router { | ||
errorFormatter: ErrorFormatter, | ||
successFormatter: SuccessFormatter, | ||
errorFormatter: ErrorFormatter; | ||
successFormatter: SuccessFormatter; | ||
setErrorFormatter(formatter: ErrorFormatter): void; | ||
@@ -218,7 +226,8 @@ setSuccessFormatter(formatter: SuccessFormatter): void; | ||
methods.forEach((method: MethodName) => { | ||
let oldImplementation = apiRouter[method]; | ||
const oldImplementation = apiRouter[method]; | ||
// tslint:disable-next-line:only-arrow-functions | ||
apiRouter[method] = function(path: PathParams, ...callbacks: (RequestHandler | RequestHandlerParams)[]) { | ||
callbacks = callbacks.map((origHandler: any, index: number) => { | ||
// return orig handler if it provides a callback | ||
if(origHandler.length >= 3) { | ||
if (origHandler.length >= 3) { | ||
return origHandler; | ||
@@ -225,0 +234,0 @@ } |
134
test.js
@@ -6,2 +6,3 @@ 'use strict'; | ||
const rp = require('request-promise'); | ||
const { Router } = require('express'); | ||
const { ExpressApiRouter, ApiError, ApiResponse, ApiErrors } = require('./dist'); | ||
@@ -22,18 +23,19 @@ const {expect, assert} = require('chai'); | ||
let app, router, server; | ||
function routeTest() { | ||
let args = Array.prototype.slice.call(arguments); | ||
const args = Array.prototype.slice.call(arguments); | ||
router.get.apply(router, ['/foo'].concat(args)); | ||
} | ||
function paramTest() { | ||
let args = Array.prototype.slice.call(arguments); | ||
let paramHandler = args.pop(); | ||
const args = Array.prototype.slice.call(arguments); | ||
const paramHandler = args.pop(); | ||
router.get.apply(router, ['/foo/:param'].concat(args)); | ||
router.param('param', paramHandler); | ||
} | ||
function requestTest(data, statusCode, extra) { | ||
return rp(`http://localhost:${port}/foo${extra||''}`).then(data => { | ||
if(data[0] === '[' || data[0] === '{') { | ||
const url = `http://localhost:${port}/foo${extra||''}`; | ||
return rp(url).then(data => { | ||
if (data[0] === '[' || data[0] === '{') { | ||
return JSON.parse(data); | ||
@@ -43,3 +45,3 @@ } | ||
}).catch(err => { | ||
if(err.statusCode != (statusCode || 500)) { | ||
if (err.statusCode != (statusCode || 500)) { | ||
throw new Error(`Status code should equal ${statusCode || 500}, was ${err.statusCode}`); | ||
@@ -50,3 +52,3 @@ } | ||
} | ||
beforeEach(cb => { | ||
@@ -69,6 +71,6 @@ app = express(); | ||
}); | ||
return requestTest('test'); | ||
}); | ||
it('should support embedded promise', () => { | ||
@@ -80,3 +82,3 @@ routeTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
@@ -86,3 +88,3 @@ foo: 'bar' | ||
}); | ||
it('should support observables', () => { | ||
@@ -92,3 +94,3 @@ routeTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
@@ -103,3 +105,3 @@ foo: 'bar' | ||
}); | ||
return requestTest({ | ||
@@ -116,3 +118,3 @@ foo: 'bar' | ||
}); | ||
return requestTest({ | ||
@@ -122,3 +124,3 @@ foo: 'bar' | ||
}); | ||
it('should support plain object with success formatter', () => { | ||
@@ -128,3 +130,3 @@ router.setSuccessFormatter(result => { | ||
}); | ||
routeTest((req, res) => { | ||
@@ -135,3 +137,3 @@ return { | ||
}); | ||
return requestTest({test: { | ||
@@ -141,3 +143,3 @@ foo: 'bar' | ||
}); | ||
it('should support direct promise', () => { | ||
@@ -149,3 +151,3 @@ routeTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
@@ -155,3 +157,3 @@ foo: 'bar' | ||
}); | ||
it('should support reporting JSON errors', () => { | ||
@@ -161,8 +163,8 @@ routeTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
error: 'test' | ||
}, 403) | ||
}, 403); | ||
}); | ||
it('should support reporting JSON errors from promise', () => { | ||
@@ -174,3 +176,3 @@ routeTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
@@ -180,3 +182,3 @@ error: 'test' | ||
}); | ||
it('should support custom error formatter', () => { | ||
@@ -186,3 +188,3 @@ router.setErrorFormatter(err => { | ||
}); | ||
routeTest((req, res) => { | ||
@@ -193,6 +195,6 @@ return Promise.delay(10).then(() => { | ||
}); | ||
return requestTest({ | ||
data: 'foo' | ||
}, 500) | ||
}, 500) | ||
}); | ||
@@ -204,3 +206,3 @@ | ||
}); | ||
routeTest((req, res) => { | ||
@@ -211,9 +213,9 @@ return Promise.delay(10).then(() => { | ||
}); | ||
return requestTest({ | ||
data: 'foo' | ||
}, 500) | ||
}); | ||
it('should report internal server error when error formatter fails', () => { | ||
@@ -223,3 +225,3 @@ router.setErrorFormatter(err => { | ||
}); | ||
routeTest((req, res) => { | ||
@@ -230,3 +232,3 @@ return Promise.delay(10).then(() => { | ||
}); | ||
return requestTest({ | ||
@@ -236,3 +238,3 @@ error: 'Internal server error' | ||
}); | ||
it('should support regular middleware', () => { | ||
@@ -245,3 +247,3 @@ routeTest((req, res, next) => { | ||
}); | ||
return requestTest({ | ||
@@ -251,3 +253,3 @@ foo: 'abc' | ||
}); | ||
it('should support param handler', () => { | ||
@@ -261,3 +263,3 @@ paramTest((req, res) => { | ||
}); | ||
return requestTest({ | ||
@@ -267,3 +269,3 @@ foo: 'xxx' | ||
}); | ||
it('should support reporting JSON errors from promise when thrown from param handler', () => { | ||
@@ -275,3 +277,3 @@ paramTest((req, res) => {}, (req, res, param) => { | ||
}); | ||
return requestTest({ | ||
@@ -288,3 +290,3 @@ error: 'test' | ||
}); | ||
return requestTest({ | ||
@@ -301,3 +303,3 @@ customResponse: 'test' | ||
}); | ||
return requestTest({ | ||
@@ -315,3 +317,3 @@ error: 'test' | ||
}); | ||
let timedOut = false; | ||
@@ -324,2 +326,42 @@ await requestTest('', 500).timeout(50).catch(Promise.TimeoutError, () => { | ||
it('should handle arrays from async value', () => { | ||
routeTest((req, res) => { | ||
return Promise.resolve(['aa']); | ||
}); | ||
return requestTest(['aa']); | ||
}); | ||
it('should handle direct arrays', () => { | ||
routeTest((req, res) => { | ||
return ['aa']; | ||
}); | ||
return requestTest(['aa']); | ||
}); | ||
it('should handle empty arrays from async function', () => { | ||
routeTest(async (req, res) => { | ||
return []; | ||
}); | ||
return requestTest([]); | ||
}); | ||
it('should handle empty arrays', () => { | ||
routeTest((req, res) => { | ||
return []; | ||
}); | ||
return requestTest([]); | ||
}); | ||
it('should handle empty objects from async function', () => { | ||
routeTest(async (req, res) => { | ||
return {}; | ||
}); | ||
return requestTest({}); | ||
}); | ||
}); |
76282
11
788
10