diff --git a/lib/clone.js b/lib/clone.js index baa6b9b..5070f67 100644 --- a/lib/clone.js +++ b/lib/clone.js @@ -15,10 +15,10 @@ module.exports = cloneAllProperties; function cloneAllProperties(data, err) { data.name = err.name; data.message = err.message; - for (var p in err) { + for (const p in err) { if ((p in data)) continue; data[p] = err[p]; } // stack is appended last to ensure order is the same for response data.stack = err.stack; -}; +} diff --git a/lib/content-negotiation.js b/lib/content-negotiation.js index ca56c5e..d7ba526 100644 --- a/lib/content-negotiation.js +++ b/lib/content-negotiation.js @@ -4,12 +4,12 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var accepts = require('accepts'); -var debug = require('debug')('strong-error-handler:http-response'); -var sendJson = require('./send-json'); -var sendHtml = require('./send-html'); -var sendXml = require('./send-xml'); -var util = require('util'); +const accepts = require('accepts'); +const debug = require('debug')('strong-error-handler:http-response'); +const sendJson = require('./send-json'); +const sendHtml = require('./send-html'); +const sendXml = require('./send-xml'); +const util = require('util'); module.exports = negotiateContentProducer; @@ -23,14 +23,14 @@ module.exports = negotiateContentProducer; * @returns {Function} Operation function with signature `fn(res, data)` */ function negotiateContentProducer(req, logWarning, options) { - var SUPPORTED_TYPES = [ + const SUPPORTED_TYPES = [ 'application/json', 'json', 'text/html', 'html', 'text/xml', 'xml', ]; options = options || {}; - var defaultType = 'json'; + let defaultType = 'json'; // checking if user provided defaultType is supported if (options.defaultType) { @@ -52,9 +52,9 @@ function negotiateContentProducer(req, logWarning, options) { // Accepts: */*, application/json, text/html ---> will resolve as application/json // eg. Chrome accepts defaults to `text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*` // In this case `resolvedContentType` will result as: `text/html` due to the order given - var resolvedContentType = accepts(req).types(SUPPORTED_TYPES); + const resolvedContentType = accepts(req).types(SUPPORTED_TYPES); debug('Resolved content-type', resolvedContentType); - var contentType = resolvedContentType || defaultType; + let contentType = resolvedContentType || defaultType; if (options.negotiateContentType === false) { if (SUPPORTED_TYPES.indexOf(options.defaultType) > -1) { @@ -71,13 +71,13 @@ function negotiateContentProducer(req, logWarning, options) { // to receive _format from user's url param to overide the content type // req.query (eg /api/Users/1?_format=json will overide content negotiation // https://github.com/strongloop/strong-remoting/blob/ac3093dcfbb787977ca0229b0f672703859e52e1/lib/http-context.js#L643-L645 - var query = req.query || {}; + const query = req.query || {}; if (query._format) { if (SUPPORTED_TYPES.indexOf(query._format) > -1) { contentType = query._format; } else { // format passed through query but not supported - var msg = util.format('Response _format "%s" is not supported' + + const msg = util.format('Response _format "%s" is not supported' + 'used "%s" instead"', query._format, defaultType); logWarning(msg); } diff --git a/lib/data-builder.js b/lib/data-builder.js index 7ba75dc..5e2cb87 100644 --- a/lib/data-builder.js +++ b/lib/data-builder.js @@ -5,8 +5,8 @@ 'use strict'; -var cloneAllProperties = require('../lib/clone.js'); -var httpStatus = require('http-status'); +const cloneAllProperties = require('../lib/clone.js'); +const httpStatus = require('http-status'); module.exports = buildResponseData; @@ -44,7 +44,7 @@ function buildResponseData(err, options) { fillSafeFields(data, err, safeFields); return data; -}; +} function serializeArrayOfErrors(errors, options) { const details = errors.map(e => buildResponseData(e, options)); diff --git a/lib/handler.js b/lib/handler.js index b05bc46..f0f746f 100644 --- a/lib/handler.js +++ b/lib/handler.js @@ -5,14 +5,14 @@ 'use strict'; -var path = require('path'); -var SG = require('strong-globalize'); +const path = require('path'); +const SG = require('strong-globalize'); SG.SetRootDir(path.resolve(__dirname, '..')); -var buildResponseData = require('./data-builder'); -var debug = require('debug')('strong-error-handler'); -var format = require('util').format; -var logToConsole = require('./logger'); -var negotiateContentProducer = require('./content-negotiation'); +const buildResponseData = require('./data-builder'); +const debug = require('debug')('strong-error-handler'); +const format = require('util').format; +const logToConsole = require('./logger'); +const negotiateContentProducer = require('./content-negotiation'); function noop() { } @@ -29,13 +29,13 @@ function createStrongErrorHandler(options) { debug('Initializing with options %j', options); // Log all errors via console.error (enabled by default) - var logError = options.log !== false ? logToConsole : noop; + const logError = options.log !== false ? logToConsole : noop; return function strongErrorHandler(err, req, res, next) { logError(req, err); writeErrorToResponse(err, req, res, options); }; -}; +} /** * Writes thrown error to response @@ -59,20 +59,20 @@ function writeErrorToResponse(err, req, res, options) { if (!err.status && !err.statusCode && res.statusCode >= 400) err.statusCode = res.statusCode; - var data = buildResponseData(err, options); + const 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); + const 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/lib/logger.js b/lib/logger.js index 44a5d64..4ce4095 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -5,8 +5,8 @@ 'use strict'; -var format = require('util').format; -var g = require('strong-globalize')(); +const format = require('util').format; +const g = require('strong-globalize')(); module.exports = function logToConsole(req, err) { if (!Array.isArray(err)) { @@ -15,9 +15,9 @@ module.exports = function logToConsole(req, err) { return; } - var errMsg = g.f('Unhandled array of errors for request %s %s\n', + const errMsg = g.f('Unhandled array of errors for request %s %s\n', req.method, req.url); - var errors = err.map(formatError).join('\n'); + const errors = err.map(formatError).join('\n'); console.error(errMsg, errors); }; diff --git a/lib/send-html.js b/lib/send-html.js index 5bcd938..b6c50a9 100644 --- a/lib/send-html.js +++ b/lib/send-html.js @@ -4,12 +4,12 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; -var ejs = require('ejs'); -var fs = require('fs'); -var path = require('path'); +const ejs = require('ejs'); +const fs = require('fs'); +const path = require('path'); -var assetDir = path.resolve(__dirname, '../views'); -var compiledTemplates = { +const assetDir = path.resolve(__dirname, '../views'); +const compiledTemplates = { // loading default template and stylesheet default: loadDefaultTemplates(), }; @@ -17,9 +17,9 @@ var compiledTemplates = { module.exports = sendHtml; function sendHtml(res, data, options) { - var toRender = {options: {}, data: data}; + const toRender = {options: {}, data: data}; // TODO: ability to call non-default template functions from options - var body = compiledTemplates.default(toRender); + const body = compiledTemplates.default(toRender); sendReponse(res, body); } @@ -30,14 +30,14 @@ function sendHtml(res, data, options) { * @returns {Function} render function with signature fn(data); */ function compileTemplate(filepath) { - var options = {cache: true, filename: filepath}; - var fileContent = fs.readFileSync(filepath, 'utf8'); + const options = {cache: true, filename: filepath}; + const fileContent = fs.readFileSync(filepath, 'utf8'); return ejs.compile(fileContent, options); } // loads and cache default error templates function loadDefaultTemplates() { - var defaultTemplate = path.resolve(assetDir, 'default-error.ejs'); + const defaultTemplate = path.resolve(assetDir, 'default-error.ejs'); return compileTemplate(defaultTemplate); } diff --git a/lib/send-json.js b/lib/send-json.js index dc6b5a2..a9f9687 100644 --- a/lib/send-json.js +++ b/lib/send-json.js @@ -5,10 +5,10 @@ 'use strict'; -var safeStringify = require('fast-safe-stringify'); +const safeStringify = require('fast-safe-stringify'); module.exports = function sendJson(res, data) { - var content = safeStringify({error: data}); + const content = safeStringify({error: data}); res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.end(content, 'utf-8'); }; diff --git a/lib/send-xml.js b/lib/send-xml.js index 57d4f7e..cbfafdc 100644 --- a/lib/send-xml.js +++ b/lib/send-xml.js @@ -5,10 +5,10 @@ 'use strict'; -var js2xmlparser = require('js2xmlparser'); +const js2xmlparser = require('js2xmlparser'); module.exports = function sendXml(res, data) { - var content = js2xmlparser.parse('error', data); + const content = js2xmlparser.parse('error', data); res.setHeader('Content-Type', 'text/xml; charset=utf-8'); res.end(content, 'utf-8'); }; diff --git a/package.json b/package.json index 6af0692..cd53e1c 100644 --- a/package.json +++ b/package.json @@ -19,20 +19,20 @@ "dependencies": { "@types/express": "^4.16.0", "accepts": "^1.3.3", - "debug": "^3.1.0", + "debug": "^4.1.1", "ejs": "^2.6.1", "fast-safe-stringify": "^2.0.6", "http-status": "^1.1.2", - "js2xmlparser": "^3.0.0", - "strong-globalize": "^4.1.0" + "js2xmlparser": "^4.0.0", + "strong-globalize": "^5.0.2" }, "devDependencies": { "chai": "^4.1.2", - "eslint": "^4.19.1", - "eslint-config-loopback": "^10.0.0", + "eslint": "^6.5.1", + "eslint-config-loopback": "^13.1.0", "express": "^4.16.3", - "mocha": "^5.2.0", - "supertest": "^3.1.0" + "mocha": "^6.2.1", + "supertest": "^4.0.2" }, "browser": { "strong-error-handler": false diff --git a/test/handler.test.js b/test/handler.test.js index 455571a..5427dae 100644 --- a/test/handler.test.js +++ b/test/handler.test.js @@ -5,13 +5,13 @@ 'use strict'; -var cloneAllProperties = require('../lib/clone.js'); -var debug = require('debug')('test'); -var expect = require('chai').expect; -var express = require('express'); -var strongErrorHandler = require('..'); -var supertest = require('supertest'); -var util = require('util'); +const cloneAllProperties = require('../lib/clone.js'); +const debug = require('debug')('test'); +const expect = require('chai').expect; +const express = require('express'); +const strongErrorHandler = require('..'); +const supertest = require('supertest'); +const util = require('util'); describe('strong-error-handler', function() { before(setupHttpServerAndClient); @@ -27,7 +27,7 @@ describe('strong-error-handler', function() { it('handles response headers already sent', function(done) { givenErrorHandlerForError(); - var handler = _requestHandler; + const handler = _requestHandler; _requestHandler = function(req, res, next) { res.end('empty'); process.nextTick(function() { @@ -70,7 +70,7 @@ describe('strong-error-handler', function() { it('handles error from `res.statusCode`', function(done) { givenErrorHandlerForError(); - var handler = _requestHandler; + const handler = _requestHandler; _requestHandler = function(req, res, next) { res.statusCode = 507; handler(req, res, next); @@ -84,7 +84,7 @@ describe('strong-error-handler', function() { }); context('logging', function() { - var logs; + let logs; beforeEach(redirectConsoleError); afterEach(restoreConsoleError); @@ -129,7 +129,7 @@ describe('strong-error-handler', function() { request.get('/api').end(function(err) { if (err) return done(err); - var msg = logs[0]; + const msg = logs[0]; // the request method expect(msg).to.contain('GET'); // the request path @@ -152,7 +152,7 @@ describe('strong-error-handler', function() { request.get('/api').end(function(err) { if (err) return done(err); - var msg = logs[0]; + const msg = logs[0]; // the request method expect(msg).to.contain('GET'); // the request path @@ -171,17 +171,17 @@ describe('strong-error-handler', function() { givenErrorHandlerForError('STRING ERROR', {log: true}); request.get('/').end(function(err) { if (err) return done(err); - var msg = logs[0]; + const msg = logs[0]; expect(msg).to.contain('STRING ERROR'); done(); }); }); - var _consoleError = console.error; + const _consoleError = console.error; function redirectConsoleError() { logs = []; console.error = function() { - var msg = util.format.apply(util, arguments); + const msg = util.format.apply(util, arguments); logs.push(msg); }; } @@ -194,7 +194,7 @@ describe('strong-error-handler', function() { context('JSON response', function() { it('contains all error properties when debug=true', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ message: 'a test error message', code: 'MACHINE_READABLE_CODE', details: 'some details', @@ -205,7 +205,7 @@ describe('strong-error-handler', function() { requestJson().end(function(err, res) { if (err) return done(err); - var expectedData = { + const expectedData = { statusCode: 500, message: 'a test error message', name: 'ErrorWithProps', @@ -222,7 +222,7 @@ describe('strong-error-handler', function() { it('includes code property for 4xx status codes when debug=false', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ statusCode: 400, message: 'error with code', name: 'ErrorWithCode', @@ -233,7 +233,7 @@ describe('strong-error-handler', function() { requestJson().end(function(err, res) { if (err) return done(err); - var expectedData = { + const expectedData = { statusCode: 400, message: 'error with code', name: 'ErrorWithCode', @@ -247,7 +247,7 @@ describe('strong-error-handler', function() { it('excludes code property for 5xx status codes when debug=false', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ statusCode: 500, code: 'MACHINE_READABLE_CODE', }); @@ -256,7 +256,7 @@ describe('strong-error-handler', function() { requestJson().end(function(err, res) { if (err) return done(err); - var expectedData = { + const expectedData = { statusCode: 500, message: 'Internal Server Error', }; @@ -268,12 +268,12 @@ describe('strong-error-handler', function() { it('contains non-enumerable Error properties when debug=true', function(done) { - var error = new Error('a test error message'); + const error = new Error('a test error message'); givenErrorHandlerForError(error, {debug: true}); requestJson().end(function(err, res) { if (err) return done(err); expect(res.body).to.have.property('error'); - var resError = res.body.error; + const resError = res.body.error; expect(resError).to.have.property('name', 'Error'); expect(resError).to.have.property('message', 'a test error message'); @@ -283,7 +283,7 @@ describe('strong-error-handler', function() { }); it('should allow setting safe fields when status=5xx', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', safeField: 'SAFE', unsafeField: 'UNSAFE', @@ -304,7 +304,7 @@ describe('strong-error-handler', function() { }); it('safe fields falls back to existing data', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', isSafe: false, }); @@ -322,7 +322,7 @@ describe('strong-error-handler', function() { }); it('should allow setting safe fields when status=4xx', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', statusCode: 422, safeField: 'SAFE', @@ -344,7 +344,7 @@ describe('strong-error-handler', function() { }); it('contains subset of properties when status=4xx', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'ValidationError', message: 'The model instance is not valid.', statusCode: 422, @@ -370,7 +370,7 @@ describe('strong-error-handler', function() { it('contains only safe info when status=5xx', function(done) { // Mock an error reported by fs.readFile - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', message: 'ENOENT: no such file or directory, open "/etc/passwd"', errno: -2, @@ -394,7 +394,7 @@ describe('strong-error-handler', function() { }); it('handles array argument as 500 when debug=false', function(done) { - var errors = [new Error('ERR1'), new Error('ERR2'), 'ERR STRING']; + const errors = [new Error('ERR1'), new Error('ERR2'), 'ERR STRING']; givenErrorHandlerForError(errors); requestJson().expect(500).end(function(err, res) { @@ -521,9 +521,9 @@ describe('strong-error-handler', function() { }); it('handles Error objects containing circular properties', function(done) { - var circularObject = {}; + const circularObject = {}; circularObject.recursiveProp = circularObject; - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ statusCode: 422, message: 'The model instance is not valid.', name: 'ValidationError', @@ -550,7 +550,7 @@ describe('strong-error-handler', function() { context('HTML response', function() { it('contains all error properties when debug=true', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ message: 'a test error message', details: 'some details', extra: 'sensitive data', @@ -607,7 +607,7 @@ describe('strong-error-handler', function() { }); it('contains subset of properties when status=4xx', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'ValidationError', message: 'The model instance is not valid.', statusCode: 422, @@ -618,7 +618,7 @@ describe('strong-error-handler', function() { requestHTML() .end(function(err, res) { expect(res.statusCode).to.eql(422); - var body = res.error.text; + const body = res.error.text; expect(body).to.match(/some details/); expect(body).to.not.match(/sensitive data/); expect(body).to.match(/ValidationError<\/title>/); @@ -629,7 +629,7 @@ describe('strong-error-handler', function() { it('contains only safe info when status=5xx', function(done) { // Mock an error reported by fs.readFile - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', message: 'ENOENT: no such file or directory, open "/etc/passwd"', errno: -2, @@ -642,7 +642,7 @@ describe('strong-error-handler', function() { requestHTML() .end(function(err, res) { expect(res.statusCode).to.eql(500); - var body = res.error.text; + const body = res.error.text; expect(body).to.not.match(/\/etc\/password/); expect(body).to.not.match(/-2/); expect(body).to.not.match(/ENOENT/); @@ -662,7 +662,7 @@ describe('strong-error-handler', function() { context('XML response', function() { it('contains all error properties when debug=true', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ message: 'a test error message', details: 'some details', extra: 'sensitive data', @@ -680,7 +680,7 @@ describe('strong-error-handler', function() { }); it('contains subset of properties when status=4xx', function(done) { - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'ValidationError', message: 'The model instance is not valid.', statusCode: 422, @@ -691,7 +691,7 @@ describe('strong-error-handler', function() { requestXML() .end(function(err, res) { expect(res.statusCode).to.eql(422); - var body = res.error.text; + const body = res.error.text; expect(body).to.match(/<details>some details<\/details>/); expect(body).to.not.match(/<extra>sensitive data<\/extra>/); expect(body).to.match(/<name>ValidationError<\/name>/); @@ -704,7 +704,7 @@ describe('strong-error-handler', function() { it('contains only safe info when status=5xx', function(done) { // Mock an error reported by fs.readFile - var error = new ErrorWithProps({ + const error = new ErrorWithProps({ name: 'Error', message: 'ENOENT: no such file or directory, open "/etc/passwd"', errno: -2, @@ -717,7 +717,7 @@ describe('strong-error-handler', function() { requestXML() .end(function(err, res) { expect(res.statusCode).to.eql(500); - var body = res.error.text; + const body = res.error.text; expect(body).to.not.match(/\/etc\/password/); expect(body).to.not.match(/-2/); expect(body).to.not.match(/ENOENT/); @@ -848,7 +848,7 @@ describe('strong-error-handler', function() { }); it('does not modify "options" argument', function(done) { - var options = {log: false, debug: false}; + const options = {log: false, debug: false}; givenErrorHandlerForError(new Error(), options); request.get('/').end(function(err) { if (err) return done(err); @@ -858,7 +858,7 @@ describe('strong-error-handler', function() { }); }); -var app, _requestHandler, request, server; +let app, _requestHandler, request, server; function resetRequestHandler() { _requestHandler = null; } @@ -874,7 +874,7 @@ function givenErrorHandlerForError(error, options) { options.log = false; } - var handler = strongErrorHandler(options); + const handler = strongErrorHandler(options); _requestHandler = function(req, res, next) { debug('Invoking strong-error-handler'); handler(error, req, res, next); @@ -885,7 +885,7 @@ function setupHttpServerAndClient(done) { app = express(); app.use(function(req, res, next) { if (!_requestHandler) { - var msg = 'Error handler middleware was not setup in this test'; + const msg = 'Error handler middleware was not setup in this test'; console.error(msg); res.statusCode = 500; res.setHeader('Content-Type', 'text/plain; charset=utf-8'); @@ -907,7 +907,7 @@ function setupHttpServerAndClient(done) { }); server = app.listen(0, function() { - var url = 'http://127.0.0.1:' + this.address().port; + const url = 'http://127.0.0.1:' + this.address().port; debug('Test server listening on %s', url); request = supertest(app); done(); @@ -924,7 +924,7 @@ function stopHttpServerAndClient() { function ErrorWithProps(props) { this.name = props.name || 'ErrorWithProps'; - for (var p in props) { + for (const p in props) { this[p] = props[p]; } @@ -936,7 +936,7 @@ function ErrorWithProps(props) { util.inherits(ErrorWithProps, Error); function getExpectedErrorData(err) { - var data = {}; + const data = {}; cloneAllProperties(data, err); return data; }