From d1dc8369ca729ca7d4c99017de8b87dbbfcde2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pster?= Date: Sun, 23 Oct 2022 00:59:12 -0300 Subject: [PATCH 01/11] feature: rescue now can handle arrays Signed-off-by: Rafael Willians --- package.json | 2 +- src/index.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 91a9dee..3f9fc7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-rescue", - "version": "1.1.32", + "version": "1.2.0", "description": "A wrapper for async functions which makes sure all async errors are passed to your errors handler", "license": "MIT", "keywords": [ diff --git a/src/index.ts b/src/index.ts index 027b1ea..70167af 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export declare type ErrorConstructor = { new(...args: any[]): Error } export declare interface Rescue { (callback: Callback): Callback from (constructor: ErrorConstructor, callback: Callback): Callback + all (callbacks: Callback[]): Callback[] } const rescue: Rescue = function rescue (callback) { @@ -36,5 +37,23 @@ rescue.from = function rescuefrom (constructor, callback) { } } +rescue.all = function rescue (callbacks) { + return callbacks.map((callback) => { + return async function rescuehandler (...args: any[]): Promise { + const next = args.slice(-1).pop() as NextFunction + + if (typeof next !== 'function') { + throw new TypeError('The last parameter received by express-rescue is not a function. Are you sure you passed its return as a middleware?') + } + + try { + await callback(...args) // eslint-disable-line + } catch (err) { + next(err) + } + } + }) +} + export default rescue module.exports = rescue From de55a059dfe43688b1ead28f927f52627a10a18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pster?= Date: Sun, 23 Oct 2022 01:20:17 -0300 Subject: [PATCH 02/11] added: readme example Signed-off-by: Rafael Willians --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index ca4d56b..5d643ff 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,14 @@ app.get('/:id', rescue(async (req, res, next) => { .json(user) })) +/** + * `rescue.all` insures thrown errors from every middleware in the array will be passed to `next` callback. + */ +app.post('/login', rescue.all([ + validateLogin, + loginController.login, +])); + /** * `rescue.from` allows you to handle a specific error which is helpful for * handling domain errors. From f516bb76d362f5c6c215c1bb26d66c3eac3e60b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pster?= Date: Sun, 23 Oct 2022 01:22:29 -0300 Subject: [PATCH 03/11] fix: english translation Signed-off-by: Rafael Willians --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d643ff..0be3eca 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ app.get('/:id', rescue(async (req, res, next) => { })) /** - * `rescue.all` insures thrown errors from every middleware in the array will be passed to `next` callback. + * `rescue.all` insures thrown errors coming from any middleware inside the array will be passed to `next` callback. */ app.post('/login', rescue.all([ validateLogin, From a113e365a05ed2ffe56b35908776c803840536bf Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:30:40 -0300 Subject: [PATCH 04/11] Simplified implementation of rescue.all --- src/index.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/index.ts b/src/index.ts index 70167af..075cb5a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,22 +37,8 @@ rescue.from = function rescuefrom (constructor, callback) { } } -rescue.all = function rescue (callbacks) { - return callbacks.map((callback) => { - return async function rescuehandler (...args: any[]): Promise { - const next = args.slice(-1).pop() as NextFunction - - if (typeof next !== 'function') { - throw new TypeError('The last parameter received by express-rescue is not a function. Are you sure you passed its return as a middleware?') - } - - try { - await callback(...args) // eslint-disable-line - } catch (err) { - next(err) - } - } - }) +rescue.all = function rescueall (callbacks) { + return callbacks.map(rescue) } export default rescue From 638f1ff8505e3d59135458779698766adf411d43 Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:31:08 -0300 Subject: [PATCH 05/11] Added tests for rescue.all --- test/rescue.test.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/rescue.test.js b/test/rescue.test.js index 1f92062..9eaf686 100644 --- a/test/rescue.test.js +++ b/test/rescue.test.js @@ -84,3 +84,24 @@ describe('rescue.from(MyError, (err) => { })', () => { rescue.from(MyError, matchedHandler)(new SomethingElse(), req, res, next) }) }) + +describe('const callables = rescue.all([fn1, fn2, fn3])', () => { + const fn1 = async (cb) => cb("ok") + const fn2 = async (_cb) => { throw new Error('foo') } + + it('All given functions are wrapped with rescue', async () => { + const [rescuedFn1, rescuedFn2] = rescue.all([fn1, fn2]) + + // Proves functions still do what they are meant to do + expect(fn1((a) => a)).to.eventually.be.equal("ok") + expect(rescuedFn1((a) => a)).to.eventually.be.equal("ok") + expect(fn2((a) => a)).to.eventually.be.rejectedWith(Error, 'foo') + expect(rescuedFn2((a) => a)).to.eventually.be.rejectedWith(Error, 'foo') + + // Proves both rescued functions contains additional behavior introduced by `rescue` + expect(fn1()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + expect(rescuedFn1()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + expect(fn2()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + expect(rescuedFn2()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + }) +}) From 17541006c19a4eeeba49a5d85b92c2ed996943d0 Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:32:01 -0300 Subject: [PATCH 06/11] Fixed formatting --- test/rescue.test.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/rescue.test.js b/test/rescue.test.js index 9eaf686..4c6b7f8 100644 --- a/test/rescue.test.js +++ b/test/rescue.test.js @@ -25,8 +25,7 @@ describe('const callable = rescue(async ([err,] req, res, next) => { })', () => }) it('Raises a TypeError if last argument is not a function', () => { - expect(route({}, {}, {}, {}, {}, {})) - .to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + expect(route({}, {}, {}, {}, {}, {})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') }) it('callable(req, res, next) - works for routes and middlewares', () => { @@ -53,8 +52,8 @@ describe('const callable = rescue(async ([err,] req, res, next) => { })', () => }) describe('rescue.from(MyError, (err) => { })', () => { - class MyError extends Error {} - class SomethingElse {} + class MyError extends Error { } + class SomethingElse { } const req = {} const res = {} From bbbaa6abaf47a3ef41cad4895b6df3b48f191b4d Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:32:21 -0300 Subject: [PATCH 07/11] Improved test case copy --- test/rescue.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rescue.test.js b/test/rescue.test.js index 4c6b7f8..bb6f31e 100644 --- a/test/rescue.test.js +++ b/test/rescue.test.js @@ -42,7 +42,7 @@ describe('const callable = rescue(async ([err,] req, res, next) => { })', () => }) }) - it('callable(foo, bar, baz, foobar, foobaz, errorHandler) - should work for basically anything, since you place an error handler as the last argument', () => { + it('callable(foo, bar, baz, foobar, foobaz, errorHandler) - should work for basically anything, as long as your function takes an error handler as the last parameter', () => { const spy = sinon.spy() route({}, {}, {}, {}, {}, {}, {}, spy).then(() => { expect(spy.called).to.equals(true) From c757c665c4c064ef513fd86743c0d87cf03146ee Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:32:42 -0300 Subject: [PATCH 08/11] Added rescue.all example to README --- README.md | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0be3eca..c9d3aec 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,6 @@ app.get('/:id', rescue(async (req, res, next) => { .json(user) })) -/** - * `rescue.all` insures thrown errors coming from any middleware inside the array will be passed to `next` callback. - */ -app.post('/login', rescue.all([ - validateLogin, - loginController.login, -])); - /** * `rescue.from` allows you to handle a specific error which is helpful for * handling domain errors. @@ -61,6 +53,24 @@ app.use((err, req, res, next) => { ``` +There's a helper function `rescue.all([...])` in case you want to wrap several functions with `rescue`. With `rescue.all`, doing `[rescue(fn1), rescue(fn2)]` can be shortened to `rescue.all([fn1, fn2])`. + +```js +const rescue = require('express-rescue') + +// Doing it like this +app.post('/products', rescue.all([ + validationFn, + createProductFn +])) + +// Is equivalent to this +app.post('/products', [ + rescue(validationFn), + rescue(createProductFn) +]) +``` + That's all. @@ -72,6 +82,7 @@ Chears! ## Tests ```txt +> express-rescue@1.2.0 test > mocha test/*.test.js --check-leaks --full-trace --use_strict --recursive const callable = rescue(async ([err,] req, res, next) => { }) @@ -80,12 +91,16 @@ Chears! ✔ Raises a TypeError if last argument is not a function ✔ callable(req, res, next) - works for routes and middlewares ✔ callable(err, req, res, next) - works for error handler middlewares - ✔ callable(foo, bar, baz, foobar, foobaz, errorHandler) - should work for basically anything, since you place an error handler as the last argument + ✔ callable(foo, bar, baz, foobar, foobaz, errorHandler) - should work for + basically anything, as long as your function takes an error handler as + the last parameter rescue.from(MyError, (err) => { }) ✔ handles the error when error is instance of given constructor ✔ it call `next` function if error is not an instance of given constructor + const callables = rescue.all([fn1, fn2, fn3]) + ✔ All given functions are wrapped with rescue - 7 passing (7ms) + 8 passing (8ms) ``` From a4f6343e207f8f9abd2d68041d663f446f64d8a8 Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 16:33:06 -0300 Subject: [PATCH 09/11] Updated package-lock.json --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6243c8d..6dfca81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "express-rescue", - "version": "1.1.32", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "express-rescue", - "version": "1.1.32", + "version": "1.2.0", "license": "MIT", "devDependencies": { "@types/express": "^4.17.13", From 3d969ad4414d0ad0afbb57f3a08cd82e6e0761e1 Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 17:12:25 -0300 Subject: [PATCH 10/11] Fixed tests that are promise-based --- test/rescue.test.js | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/test/rescue.test.js b/test/rescue.test.js index bb6f31e..a75c0c5 100644 --- a/test/rescue.test.js +++ b/test/rescue.test.js @@ -25,26 +25,26 @@ describe('const callable = rescue(async ([err,] req, res, next) => { })', () => }) it('Raises a TypeError if last argument is not a function', () => { - expect(route({}, {}, {}, {}, {}, {})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + return Promise.all([ + expect(route({})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function'), + expect(route({}, {})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function'), + expect(route({}, {}, {})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function'), + expect(route({}, {}, {}, {})).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + ]) }) it('callable(req, res, next) - works for routes and middlewares', () => { const spy = sinon.spy() - route({}, {}, spy).then(() => { + + return route({}, {}, spy).then(() => { expect(spy.called).to.equals(true) }) }) it('callable(err, req, res, next) - works for error handler middlewares', () => { const spy = sinon.spy() - route({}, {}, {}, spy).then(() => { - expect(spy.called).to.equals(true) - }) - }) - it('callable(foo, bar, baz, foobar, foobaz, errorHandler) - should work for basically anything, as long as your function takes an error handler as the last parameter', () => { - const spy = sinon.spy() - route({}, {}, {}, {}, {}, {}, {}, spy).then(() => { + return route({}, {}, {}, spy).then(() => { expect(spy.called).to.equals(true) }) }) @@ -85,22 +85,15 @@ describe('rescue.from(MyError, (err) => { })', () => { }) describe('const callables = rescue.all([fn1, fn2, fn3])', () => { - const fn1 = async (cb) => cb("ok") - const fn2 = async (_cb) => { throw new Error('foo') } - - it('All given functions are wrapped with rescue', async () => { - const [rescuedFn1, rescuedFn2] = rescue.all([fn1, fn2]) - - // Proves functions still do what they are meant to do - expect(fn1((a) => a)).to.eventually.be.equal("ok") - expect(rescuedFn1((a) => a)).to.eventually.be.equal("ok") - expect(fn2((a) => a)).to.eventually.be.rejectedWith(Error, 'foo') - expect(rescuedFn2((a) => a)).to.eventually.be.rejectedWith(Error, 'foo') - - // Proves both rescued functions contains additional behavior introduced by `rescue` - expect(fn1()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') - expect(rescuedFn1()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') - expect(fn2()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') - expect(rescuedFn2()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') + const fn = async (_cb) => { throw new Error('foo') } + + it('All given functions are wrapped with rescue', () => { + const [rescuedFn] = rescue.all([fn]) + + return Promise.all([ + // Proves that the rescued function contains additional behavir that is + // added when a fn is wrapped with `rescue`. + expect(rescuedFn()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function'), + ]) }) }) From f699c97ff34392339cd990ef974e99c071d1e4b6 Mon Sep 17 00:00:00 2001 From: Rafael Willians Date: Fri, 28 Oct 2022 17:17:21 -0300 Subject: [PATCH 11/11] Lint fix --- package.json | 1 + test/rescue.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f9fc7e..d77af31 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "coverage": "nyc --reporter=text npm test", "coverage:report": "nyc report --reporter=lcov", "lint": "standard --global describe --global it", + "lint:fix": "standard --global describe --global it --fix", "test": "mocha test/*.test.js --check-leaks --full-trace --use_strict --recursive" }, "devDependencies": { diff --git a/test/rescue.test.js b/test/rescue.test.js index a75c0c5..d95902a 100644 --- a/test/rescue.test.js +++ b/test/rescue.test.js @@ -93,7 +93,7 @@ describe('const callables = rescue.all([fn1, fn2, fn3])', () => { return Promise.all([ // Proves that the rescued function contains additional behavir that is // added when a fn is wrapped with `rescue`. - expect(rescuedFn()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function'), + expect(rescuedFn()).to.eventually.be.rejectedWith(TypeError, 'The last parameter received by express-rescue is not a function') ]) }) })