diff --git a/lib/hooks/responses/defaults/badRequest.js b/lib/hooks/responses/defaults/badRequest.js index f5818d7a2..13331126c 100644 --- a/lib/hooks/responses/defaults/badRequest.js +++ b/lib/hooks/responses/defaults/badRequest.js @@ -2,7 +2,7 @@ * Module dependencies */ -var buildOutletFunction = require('../helpers/build-outlet-function'); +var _ = require('@sailshq/lodash'); @@ -23,16 +23,53 @@ var buildOutletFunction = require('../helpers/build-outlet-function'); * ``` */ -module.exports = function badRequest(data, options) { +module.exports = function badRequest(data) { - var config = { - logMessage: 'Sending 400 ("Bad Request") response', - statusCode: 400, - logData: true, - isError: true, - isGuessView: true - }; + // Get access to `req` and `res` + var req = this.req; + var res = this.res; - buildOutletFunction(this.req, this.res, data, options, config); + // Get access to `sails` + var sails = req._sails; + + // Log error to console + if (data !== undefined) { + sails.log.verbose('Sending 400 ("Bad Request") response: \n', data); + } + + // Set status code + res.status(400); + + // If appropriate, serve data as JSON. + if (req.wantsJSON) { + // If the data is an error instance and it doesn't have a custom .toJSON(), + // use its stack instead (otherwise res.json() will turn it into an empty dictionary). + if (_.isError(data)) { + if (!_.isFunction(data.toJSON)) { + data = data.stack; + } + } + return res.json(data); + } + + return res.view('400', { data: data }, function (err, html) { + + // If a view error occured, fall back to JSON. + if (err) { + // + // Additionally: + // • If the view was missing, ignore the error but provide a verbose log. + if (err.code === 'E_VIEW_FAILED') { + sails.log.verbose('res.badRequest() :: Could not locate view for error page (sending JSON instead). Details: ', err); + } + // Otherwise, if this was a more serious error, log to the console with the details. + else { + sails.log.warn('res.badRequest() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); + } + return res.json(data); + } + + return res.send(html); + }); }; diff --git a/lib/hooks/responses/defaults/forbidden.js b/lib/hooks/responses/defaults/forbidden.js index d8f8cc97e..4b43ddccc 100644 --- a/lib/hooks/responses/defaults/forbidden.js +++ b/lib/hooks/responses/defaults/forbidden.js @@ -2,7 +2,7 @@ * Module dependencies */ -var buildOutletFunction = require('../helpers/build-outlet-function'); +var _ = require('@sailshq/lodash'); @@ -20,17 +20,41 @@ var buildOutletFunction = require('../helpers/build-outlet-function'); * ``` */ -module.exports = function forbidden (data, options) { - - var config = { - logMessage: 'Sending 403 ("Forbidden") response', - statusCode: 403, - logData: true, - isError: true, - isGuessView: false, - name: 'forbidden' - }; - - buildOutletFunction(this.req, this.res, data, options, config); +module.exports = function forbidden () { + + // Get access to `req` and `res` + var req = this.req; + var res = this.res; + + // Get access to `sails` + var sails = req._sails; + + // Set status code + res.status(403); + + // If appropriate, serve data as JSON. + if (req.wantsJSON) { + return res.send(); + } + + return res.view('403', function (err, html) { + + // If a view error occured, fall back to JSON. + if (err) { + // + // Additionally: + // • If the view was missing, ignore the error but provide a verbose log. + if (err.code === 'E_VIEW_FAILED') { + sails.log.verbose('res.forbidden() :: Could not locate view for error page (sending text instead). Details: ', err); + } + // Otherwise, if this was a more serious error, log to the console with the details. + else { + sails.log.warn('res.forbidden() :: When attempting to render error page view, an error occured (sending text instead). Details: ', err); + } + return res.send('FORBIDDEN'); + } + + return res.send(html); + }); }; diff --git a/lib/hooks/responses/defaults/notFound.js b/lib/hooks/responses/defaults/notFound.js index a3d94ae13..b09785d82 100644 --- a/lib/hooks/responses/defaults/notFound.js +++ b/lib/hooks/responses/defaults/notFound.js @@ -2,7 +2,7 @@ * Module dependencies */ -var buildOutletFunction = require('../helpers/build-outlet-function'); +var _ = require('@sailshq/lodash'); @@ -25,17 +25,41 @@ var buildOutletFunction = require('../helpers/build-outlet-function'); * automatically. */ -module.exports = function notFound (data, options) { +module.exports = function notFound (data) { - var config = { - logMessage: 'Sending 404 ("Not Found") response', - statusCode: 404, - logData: true, - isError: true, - isGuessView: false, - name: 'notFound' - }; + // Get access to `req` and `res` + var req = this.req; + var res = this.res; - buildOutletFunction(this.req, this.res, data, options, config); + // Get access to `sails` + var sails = req._sails; + + // Set status code + res.status(404); + + // If appropriate, serve data as JSON. + if (req.wantsJSON) { + return res.send(); + } + + return res.view('404', function (err, html) { + + // If a view error occured, fall back to JSON. + if (err) { + // + // Additionally: + // • If the view was missing, ignore the error but provide a verbose log. + if (err.code === 'E_VIEW_FAILED') { + sails.log.verbose('res.notFound() :: Could not locate view for error page (sending text instead). Details: ', err); + } + // Otherwise, if this was a more serious error, log to the console with the details. + else { + sails.log.warn('res.notFound() :: When attempting to render error page view, an error occured (sending text instead). Details: ', err); + } + return res.send('NOT FOUND'); + } + + return res.send(html); + }); }; diff --git a/lib/hooks/responses/defaults/ok.js b/lib/hooks/responses/defaults/ok.js index ca597279c..2320b1550 100644 --- a/lib/hooks/responses/defaults/ok.js +++ b/lib/hooks/responses/defaults/ok.js @@ -2,7 +2,7 @@ * Module dependencies */ -var buildOutletFunction = require('../helpers/build-outlet-function'); +var _ = require('@sailshq/lodash'); @@ -19,17 +19,26 @@ var buildOutletFunction = require('../helpers/build-outlet-function'); * - pass string to render specified view */ -module.exports = function sendOK (data, options) { +module.exports = function sendOK (data) { - var config = { - logMethod: 'silly', - logMessage: 'res.ok() :: Sending 200 ("OK") response', - statusCode: 200, - logData: false, - isError: false, - isGuessView: true - }; + // Get access to `req` and `res` + var req = this.req; + var res = this.res; - buildOutletFunction(this.req, this.res, data, options, config); + // Get access to `sails` + var sails = req._sails; + + // Set status code + res.status(200); + + // If the data is an error instance and it doesn't have a custom .toJSON(), + // use its stack instead (otherwise res.json() will turn it into an empty dictionary). + if (_.isError(data)) { + if (!_.isFunction(data.toJSON)) { + data = data.stack; + } + } + + return res.json(data); }; diff --git a/lib/hooks/responses/defaults/serverError.js b/lib/hooks/responses/defaults/serverError.js index d0e821ce6..f8ae56436 100644 --- a/lib/hooks/responses/defaults/serverError.js +++ b/lib/hooks/responses/defaults/serverError.js @@ -2,7 +2,7 @@ * Module dependencies */ -var buildOutletFunction = require('../helpers/build-outlet-function'); +var _ = require('@sailshq/lodash'); @@ -20,18 +20,58 @@ var buildOutletFunction = require('../helpers/build-outlet-function'); * automatically. */ -module.exports = function serverError (data, options) { +module.exports = function serverError (data) { - var config = { - logMethod: 'error', - logMessage: 'Sending 500 ("Server Error") response', - statusCode: 500, - logData: true, - isError: true, - isGuessView: false, - name: 'serverError' - }; + // Get access to `req` and `res` + var req = this.req; + var res = this.res; - buildOutletFunction(this.req, this.res, data, options, config); + // Get access to `sails` + var sails = req._sails; + + // Log error to console + if (data !== undefined) { + sails.log.error('Sending 500 ("Server Error") response: \n', data); + } + + // Don't output error data with response in production. + if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { + data = undefined; + } + + // Set status code + res.status(500); + + // If appropriate, serve data as JSON. + if (req.wantsJSON) { + // If the data is an error instance and it doesn't have a custom .toJSON(), + // use its stack instead (otherwise res.json() will turn it into an empty dictionary). + if (_.isError(data)) { + if (!_.isFunction(data.toJSON)) { + data = data.stack; + } + } + return res.json(data); + } + + return res.view('500', { error: data }, function (err, html) { + + // If a view error occured, fall back to JSON. + if (err) { + // + // Additionally: + // • If the view was missing, ignore the error but provide a verbose log. + if (err.code === 'E_VIEW_FAILED') { + sails.log.verbose('res.serverError() :: Could not locate view for error page (sending JSON instead). Details: ', err); + } + // Otherwise, if this was a more serious error, log to the console with the details. + else { + sails.log.warn('res.serverError() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); + } + return res.json(data); + } + + return res.send(html); + }); }; diff --git a/lib/hooks/responses/helpers/build-outlet-function.js b/lib/hooks/responses/helpers/build-outlet-function.js deleted file mode 100644 index fca2b11f0..000000000 --- a/lib/hooks/responses/helpers/build-outlet-function.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * buildOutletFunction() - * - * Return an outlet function which can be called to terminate a response. - * - * @param {[type]} req [description] - * @param {[type]} res [description] - * @param {[type]} data [description] - * @param {[type]} options [description] - * @param {[type]} config [description] - * - * @return {Function} an outlet function which sends a response - * - * @api private - */ -module.exports = function buildOutletFunction (req, res, data, options, config) { - - // Get access to `sails` - var sails = req._sails; - config.logMethod = config.logMethod || 'verbose'; - - // Log error to console - if (config.logData && data !== undefined) { - sails.log[config.logMethod](config.logMessage+': \n', data); - } else { - sails.log[config.logMethod](config.logMessage); - } - - // Set status code - res.status(config.statusCode); - - if(config.isError) { - // Only include errors in response if application environment - // is not set to 'production'. In production, we shouldn't - // send back any identifying information about errors. - if (sails.config.environment === 'production' && sails.config.keepResponseErrors !== true) { - data = undefined; - } - } - - // If appropriate, serve data as JSON(P) - if (req.wantsJSON || sails.config.hooks.views === false) { - return res.jsonx(data); - } - - // If second argument is a string, we take that to mean it refers to a view. - // If it was omitted, use an empty object (`{}`) - options = (typeof options === 'string') ? { view: options } : options || {}; - - // If a view was provided in options, serve it. - // Otherwise try to guess an appropriate view, or if that doesn't - // work, just send JSON. - if (options.view) { - return res.view(options.view, { data: data }); - } - - // If no second argument provided, try to serve the implied view, - // but fall back to sending JSON(P) if no view can be inferred. - else { - if(config.isGuessView) { - return res.guessView({ data: data }, function couldNotGuessView () { - return res.jsonx(data); - }); - } else { - return res.view(config.statusCode, { data: data }, function (err, html) { - - // If a view error occured, fall back to JSON(P). - if (err) { - // - // Additionally: - // • If the view was missing, ignore the error but provide a verbose log. - if (err.code === 'E_VIEW_FAILED') { - sails.log.verbose('res.'+config.name+'() :: Could not locate view for error page (sending JSON instead). Details: ', err); - } - // Otherwise, if this was a more serious error, log to the console with the details. - else { - sails.log.warn('res.'+config.name+'() :: When attempting to render error page view, an error occured (sending JSON instead). Details: ', err); - } - return res.jsonx(data); - } - - return res.send(html); - }); - } - } -}; diff --git a/lib/router/bindDefaultHandlers.js b/lib/router/bindDefaultHandlers.js index 04814643c..515e89554 100644 --- a/lib/router/bindDefaultHandlers.js +++ b/lib/router/bindDefaultHandlers.js @@ -48,14 +48,14 @@ module.exports = function(sails) { } - // Next, try to use `res.negotiate()`, if it exists and is valid. + // Next, try to use `res.serverError()`, if it exists and is valid. try { - if (typeof res.negotiate === 'function') { - return res.negotiate(err); + if (typeof res.serverError === 'function') { + return res.serverError(err); }//>- - } catch (e) { /* ignore any unexpected error encountered when attempting to respond w/ res.negotiate(). */ } + } catch (e) { /* ignore any unexpected error encountered when attempting to respond w/ res.serverError(). */ } // Catch-all: // Log a message and try to use `res.send` to respond. diff --git a/test/unit/req.errors.test.js b/test/unit/req.errors.test.js index 1b34e5627..f7ed09db9 100644 --- a/test/unit/req.errors.test.js +++ b/test/unit/req.errors.test.js @@ -3,7 +3,7 @@ */ var assert = require('assert'); - +var _ = require('@sailshq/lodash'); var $Sails = require('../helpers/sails'); @@ -71,6 +71,13 @@ describe('request that causes an error', function (){ var MESSAGE = 'oh no I forgot my keys again'; var ERROR = new Error(MESSAGE); + ERROR.toJSON = function() { + return { + message: MESSAGE, + stack: this.stack + }; + }; + sails.get('/errors/3', function (req, res) { throw ERROR; });