diff --git a/README.md b/README.md index da92ab0..9792187 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,29 @@ app.use(errorHandler({ app.listen(3000); ``` +The module also exports `writeErrorToResponse`, a non-middleware flavor of the +error handler: + +```js +const http = require('http'); +const writeErrorToResponse = require('strong-error-handler') + .writeErrorToResponse; +const errHandlingOptions = {debug: process.env.NODE_ENV === 'development'} + +http + .createServer((req, res) => { + if (errShouldBeThrown) { + writeErrorToResponse( + new Error('something went wrong'), + req, + res, + errHandlingOptions, + ); + } + }) + .listen(3000); +``` + In LoopBack applications, add the following entry to `server/middleware.json`: ```json diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..646c652 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,64 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: strong-error-handler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Type definitions for strong-error-handler 3.x +// Project: https://github.com/strongloop/strong-error-handler +// Definitions by: Kyusung Shim +// TypeScript Version: 3.0 + +import * as Express from 'express'; + +export = errorHandlerFactory; + +/** + * Creates a middleware function for error-handling + * @param options Options for error handler settings + */ +declare function errorHandlerFactory( + options?: errorHandlerFactory.ErrorHandlerOptions +): errorHandlerFactory.StrongErrorHandler; + +declare namespace errorHandlerFactory { + /** + * Writes thrown error to response + * @param err Error to handle + * @param req Incoming request + * @param res Response + * @param options Options for error handler settings + */ + function writeErrorToResponse( + err: Error, + req: Express.Request, + res: Express.Response, + options?: ErrorWriterOptions + ): void; + + /** + * Error-handling middleware function. Includes server-side logging + */ + type StrongErrorHandler = ( + err: Error, + req: Express.Request, + res: Express.Response, + next: (err?: any) => void + ) => void; + + /** + * Options for writing errors to the response + */ + interface ErrorWriterOptions { + debug?: boolean; + safeFields?: string[]; + defaultType?: string; + negotiateContentType?: boolean; + } + + /** + * Options for error-handling + */ + interface ErrorHandlerOptions extends ErrorWriterOptions { + log?: boolean; + } +} diff --git a/lib/handler.js b/lib/handler.js index 8aa2004..036a0ea 100644 --- a/lib/handler.js +++ b/lib/handler.js @@ -23,7 +23,7 @@ function noop() { * @param {Object} options * @returns {Function} */ -exports = module.exports = function createStrongErrorHandler(options) { +function createStrongErrorHandler(options) { options = options || {}; debug('Initializing with options %j', options); @@ -32,31 +32,47 @@ exports = module.exports = function createStrongErrorHandler(options) { var logError = options.log !== false ? logToConsole : noop; return function strongErrorHandler(err, req, res, next) { - debug('Handling %s', err.stack || err); - logError(req, err); - - if (res._header) { - debug('Response was already sent, closing the underlying connection'); - return req.socket.destroy(); - } - - // this will alter the err object, to handle when res.statusCode is an error - if (!err.status && !err.statusCode && res.statusCode >= 400) - err.statusCode = res.statusCode; - - var data = buildResponseData(err, options); - debug('Response status %s data %j', data.statusCode, data); - - res.setHeader('X-Content-Type-Options', 'nosniff'); - res.statusCode = data.statusCode; - - var sendResponse = negotiateContentProducer(req, warn, options); - sendResponse(res, data); - - function warn(msg) { - res.header('X-Warning', msg); - debug(msg); - } + writeErrorToResponse(err, req, res, options); }; }; + +/** + * Writes thrown error to response + * + * @param {Error} err + * @param {Express.Request} req + * @param {Express.Response} res + * @param {Object} options + */ +function writeErrorToResponse(err, req, res, options) { + debug('Handling %s', err.stack || err); + + options = options || {}; + + if (res._header) { + debug('Response was already sent, closing the underlying connection'); + return req.socket.destroy(); + } + + // this will alter the err object, to handle when res.statusCode is an error + if (!err.status && !err.statusCode && res.statusCode >= 400) + err.statusCode = res.statusCode; + + var data = buildResponseData(err, options); + debug('Response status %s data %j', data.statusCode, data); + + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.statusCode = data.statusCode; + + var sendResponse = negotiateContentProducer(req, warn, options); + sendResponse(res, data); + + function warn(msg) { + res.header('X-Warning', msg); + debug(msg); + } +}; + +exports = module.exports = createStrongErrorHandler; +exports.writeErrorToResponse = writeErrorToResponse; diff --git a/package.json b/package.json index 52ae831..5390642 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "posttest": "npm run lint" }, "dependencies": { + "@types/express": "^4.16.0", "accepts": "^1.3.3", "debug": "^3.1.0", "ejs": "^2.6.1", diff --git a/test/handler.test.js b/test/handler.test.js index 653f7c3..06de6c2 100644 --- a/test/handler.test.js +++ b/test/handler.test.js @@ -78,7 +78,8 @@ describe('strong-error-handler', function() { request.get('/').expect( 507, {error: {statusCode: 507, message: 'Insufficient Storage'}}, - done); + done + ); }); }); @@ -145,7 +146,8 @@ describe('strong-error-handler', function() { it('handles array argument', function(done) { givenErrorHandlerForError( [new TypeError('ERR1'), new Error('ERR2')], - {log: true}); + {log: true} + ); request.get('/api').end(function(err) { if (err) return done(err); @@ -565,8 +567,7 @@ describe('strong-error-handler', function() { ); done(); }); - } - ); + }); it('HTML-escapes all 5xx response properties in development mode', function(done) { @@ -582,8 +583,7 @@ describe('strong-error-handler', function() { /500(.*?)a test error message<img onerror=alert\(1\) src=a>/, done ); - } - ); + }); it('contains subset of properties when status=4xx', function(done) { var error = new ErrorWithProps({ @@ -750,8 +750,7 @@ describe('strong-error-handler', function() { request.get('/') .set('Accept', 'text/html') .expect('Content-Type', /^application\/json/, done); - } - ); + }); it('chooses resolved type when negotiateContentType=false + not-supported', function(done) { @@ -762,8 +761,7 @@ describe('strong-error-handler', function() { request.get('/') .set('Accept', 'text/html') .expect('Content-Type', /^text\/html/, done); - } - ); + }); it('chooses default type when negotiateContentType=false + not-supported ', function(done) { @@ -773,8 +771,7 @@ describe('strong-error-handler', function() { }); request.get('/') .expect('Content-Type', /^application\/json/, done); - } - ); + }); it('honors order of accepted content-types of text/html', function(done) { givenErrorHandlerForError(new Error('Some error'), {