Skip to content

Commit 8a1cd4c

Browse files
authored
Merge pull request #2 from aoxrud-ww/patch-1
customErrors and customizable errorHandler
2 parents 1956894 + 9e935d7 commit 8a1cd4c

File tree

6 files changed

+135
-100
lines changed

6 files changed

+135
-100
lines changed

README.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,30 @@ app.use(errorHandler({ debug: true }))
3737

3838
You may throw any of the following types of errors with an optional error `message`.
3939

40-
### `throw validationError( message )`
40+
### `throw validationError( message, details )`
4141

4242
- Appropriate for missing required fields or invalid input.
43-
- Sets the response status code to `400`.
43+
- Sets the response status code to `400`.
4444

45-
### `throw authRequired( message )`
45+
### `throw authRequired( message, details )`
4646

4747
- Appropriate when token or authentication is missing.
48-
- Sets the response status code to `401`.
48+
- Sets the response status code to `401`.
4949

50-
### `throw accessDenied( message )`
50+
### `throw accessDenied( message, details )`
5151

5252
- Appropriate when the user making the request does not have permission to the resource.
53-
- Sets the response status code to `403`.
53+
- Sets the response status code to `403`.
5454

55-
### `throw notFound( message )`
55+
### `throw notFound( message, details )`
5656

5757
- Appropriate when the requested resource does not exist.
58-
- Sets the response status code to `404`.
58+
- Sets the response status code to `404`.
59+
60+
61+
### `throw customError( message, details, httpStatus )`
62+
63+
- Appropriate for when custom errors are necessary
5964

6065
## Handling Errors
6166

@@ -69,4 +74,16 @@ app.use(errorHandler({ debug: true }))
6974

7075
### Error Handler Options
7176

72-
`debug` {Boolean} - returns debug output (stack trace, request data)
77+
- `debug` {Boolean} - (optional) returns debug output (stack trace, request data)
78+
- `onError` {Function} - (optional) intercept error before sending to client
79+
80+
81+
```js
82+
app.use(errorHandler({
83+
debug: true,
84+
transform: ({ err, req, res, responseBody }) => {
85+
res.setHeader('X-Warning', 'Stop sending bad requests.')
86+
return responseBody;
87+
}
88+
}))
89+
```

dist/createError.js

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,45 @@
33
Object.defineProperty(exports, "__esModule", {
44
value: true
55
});
6-
exports.notFound = exports.accessDenied = exports.authRequired = exports.validationError = void 0;
6+
exports.notFound = exports.accessDenied = exports.authRequired = exports.validationError = exports.customError = void 0;
77

8-
// 400
9-
var validationError = function validationError(message) {
10-
var err = new Error(message || 'Validation error.');
11-
err.validationError = true;
8+
var customError = function customError(message, details, httpStatus) {
9+
var err = new Error(message);
10+
err.details = details;
11+
err.httpStatus = httpStatus;
1212
return err;
13-
}; // 401
13+
};
1414

15+
exports.customError = customError;
1516

16-
exports.validationError = validationError;
17+
var validationError = function validationError() {
18+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Validation error.';
19+
var details = arguments.length > 1 ? arguments[1] : undefined;
20+
return customError(message, details, 400);
21+
};
1722

18-
var authRequired = function authRequired(message) {
19-
var err = new Error(message || 'Authentication required.');
20-
err.authRequired = true;
21-
return err;
22-
}; // 403
23+
exports.validationError = validationError;
2324

25+
var authRequired = function authRequired() {
26+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Authentication required.';
27+
var details = arguments.length > 1 ? arguments[1] : undefined;
28+
return customError(message, details, 401);
29+
};
2430

2531
exports.authRequired = authRequired;
2632

27-
var accessDenied = function accessDenied(message) {
28-
var err = new Error(message || 'Access denied.');
29-
err.accessDenied = true;
30-
return err;
31-
}; // 404
32-
33+
var accessDenied = function accessDenied() {
34+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Access denied.';
35+
var details = arguments.length > 1 ? arguments[1] : undefined;
36+
return customError(message, details, 403);
37+
};
3338

3439
exports.accessDenied = accessDenied;
3540

36-
var notFound = function notFound(message) {
37-
var err = new Error(message || 'Not found.');
38-
err.notFound = true;
39-
return err;
41+
var notFound = function notFound() {
42+
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Not found.';
43+
var details = arguments.length > 1 ? arguments[1] : undefined;
44+
return customError(message, details, 404);
4045
};
4146

4247
exports.notFound = notFound;

dist/errorHandler.js

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,26 @@ Object.defineProperty(exports, "__esModule", {
55
});
66
exports["default"] = void 0;
77

8+
var defaultTransformer = function defaultTransformer(_ref) {
9+
var err = _ref.err,
10+
req = _ref.req,
11+
res = _ref.res,
12+
responseBody = _ref.responseBody;
13+
return responseBody;
14+
};
15+
816
var _default = function _default() {
9-
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
10-
_ref$debug = _ref.debug,
11-
debug = _ref$debug === void 0 ? false : _ref$debug;
17+
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
18+
_ref2$debug = _ref2.debug,
19+
debug = _ref2$debug === void 0 ? false : _ref2$debug,
20+
_ref2$transform = _ref2.transform,
21+
transform = _ref2$transform === void 0 ? defaultTransformer : _ref2$transform;
1222

1323
return function (err, req, res, next) {
14-
var statusCode = 500;
15-
16-
if (err.validationError) {
17-
statusCode = 400;
18-
}
19-
20-
if (err.authRequired) {
21-
statusCode = 401;
22-
}
23-
24-
if (err.accessDenied) {
25-
statusCode = 403;
26-
}
27-
28-
if (err.notFound) {
29-
statusCode = 404;
30-
}
31-
32-
var error = {
33-
message: err.message
24+
var statusCode = err.httpStatus || 500;
25+
var responseBody = {
26+
message: err.message,
27+
details: err.details
3428
};
3529
var stack;
3630

@@ -45,7 +39,7 @@ var _default = function _default() {
4539
}
4640

4741
if (debug) {
48-
error.debug = {
42+
responseBody.debug = {
4943
stack: stack,
5044
request: {
5145
method: req.method,
@@ -58,10 +52,16 @@ var _default = function _default() {
5852

5953

6054
if (err.body) {
61-
error.message = 'Could not parse JSON body.';
55+
responseBody.message = 'Could not parse JSON body.';
6256
}
6357

64-
res.status(statusCode).json(error);
58+
responseBody = transform({
59+
err: err,
60+
req: req,
61+
res: res,
62+
responseBody: responseBody
63+
});
64+
res.status(statusCode).json(responseBody);
6565
};
6666
};
6767

src/createError.js

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
1-
// 400
2-
export const validationError = (message) => {
3-
const err = new Error(message || 'Validation error.')
4-
err.validationError = true
5-
return err
1+
export const customError = (message, details, httpStatus) => {
2+
const err = new Error(message);
3+
err.details = details;
4+
err.httpStatus = httpStatus;
5+
return err;
66
}
77

8-
// 401
9-
export const authRequired = (message) => {
10-
const err = new Error(message || 'Authentication required.')
11-
err.authRequired = true
12-
return err
13-
}
8+
export const validationError = (message = 'Validation error.', details) =>
9+
customError(message, details, 400)
1410

15-
// 403
16-
export const accessDenied = (message) => {
17-
const err = new Error(message || 'Access denied.')
18-
err.accessDenied = true
19-
return err
20-
}
11+
export const authRequired = (message = 'Authentication required.', details) =>
12+
customError(message, details, 401)
13+
14+
export const accessDenied = (message = 'Access denied.', details) =>
15+
customError(message, details, 403);
2116

22-
// 404
23-
export const notFound = (message) => {
24-
const err = new Error(message || 'Not found.')
25-
err.notFound = true
26-
return err
27-
}
17+
export const notFound = (message = 'Not found.', details) =>
18+
customError(message, details, 404);

src/errorHandler.js

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
export default ({ debug = false } = {}) => (err, req, res, next) => {
2-
let statusCode = 500
3-
if (err.validationError) {
4-
statusCode = 400
5-
}
6-
if (err.authRequired) {
7-
statusCode = 401
8-
}
9-
if (err.accessDenied) {
10-
statusCode = 403
11-
}
12-
if (err.notFound) {
13-
statusCode = 404
14-
}
15-
const error = {
16-
message: err.message
17-
}
1+
const defaultTransformer = ({ err, req, res, responseBody }) => responseBody;
2+
3+
export default ({ debug = false, transform = defaultTransformer } = {}) => (err, req, res, next) => {
4+
const statusCode = err.httpStatus || 500
5+
let responseBody = {
6+
message: err.message,
7+
details: err.details
8+
}
9+
1810
let stack
1911
if (err.stack) {
2012
stack = err.stack.split('\n')
@@ -24,7 +16,7 @@ export default ({ debug = false } = {}) => (err, req, res, next) => {
2416
.map(line => line.trim())
2517
}
2618
if (debug) {
27-
error.debug = {
19+
responseBody.debug = {
2820
stack,
2921
request: {
3022
method: req.method,
@@ -36,7 +28,8 @@ export default ({ debug = false } = {}) => (err, req, res, next) => {
3628
}
3729
// body-parser error
3830
if (err.body) {
39-
error.message = 'Could not parse JSON body.'
31+
responseBody.message = 'Could not parse JSON body.'
4032
}
41-
res.status(statusCode).json(error)
33+
responseBody = transform({ err, req, res, responseBody });
34+
res.status(statusCode).json(responseBody)
4235
}

test/spec.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ const {
33
accessDenied,
44
authRequired,
55
notFound,
6+
customError,
67
errorHandler
78
} = require('..')
89

910
const express = require('express')
1011
const supertest = require('supertest')
1112

1213
const app = express()
14+
const currentTime = Date.now();
15+
const teapotError = () =>
16+
customError("I'm a teapot", null, 418);
17+
const tooEarlyError = () =>
18+
customError("It is too early for errors", {
19+
time: currentTime
20+
}, 425);
1321

1422
app.get('/:test', (req, res, next) => {
1523
try {
@@ -19,6 +27,8 @@ app.get('/:test', (req, res, next) => {
1927
case '401': throw authRequired()
2028
case '403': throw accessDenied()
2129
case '404': throw notFound()
30+
case '418': throw teapotError()
31+
case '425': throw tooEarlyError()
2232
}
2333
res.json({ success: true })
2434
} catch (err) {
@@ -81,3 +91,22 @@ test('GET /404', done => {
8191
})
8292
.end(done)
8393
})
94+
95+
test('GET /418', done => {
96+
supertest(app)
97+
.get('/418')
98+
.expect(418)
99+
.end(done)
100+
})
101+
102+
103+
test('GET error with details', done => {
104+
supertest(app)
105+
.get('/425')
106+
.expect(425)
107+
.expect(res => {
108+
if (res.body.details.time !== currentTime)
109+
throw new Error('the error.details were not provided')
110+
})
111+
.end(done)
112+
})

0 commit comments

Comments
 (0)