UNPKG

@balderdash/sails-edge

Version:

API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)

207 lines (164 loc) 6.24 kB
/** * Dependencies */ var _ = require('lodash'), Err = require('../../../errors/fatal'), onRoute = require('./onRoute'), STRINGFILE = require('sails-stringfile'); /** * Expose hook definition */ module.exports = function(sails) { return { defaults: {}, configure: function() { // Legacy (< v0.10) support for configured handlers if (typeof sails.config[500] === 'function') { sails.after('lifted', function() { STRINGFILE.logDeprecationNotice('sails.config[500]', STRINGFILE.get('links.docs.migrationGuide.responses'), sails.log.debug); sails.log.debug('sails.config[500] (i.e. `config/500.js`) has been superceded in Sails v0.10.'); sails.log.debug('Please define a "response" instead. (i.e. api/responses/serverError.js)'); sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)'); sails.log.debug('If you\'d like to use the default handler, just remove this configuration option.'); }); } if (typeof sails.config[404] === 'function') { sails.after('lifted', function() { STRINGFILE.logDeprecationNotice('sails.config[404]', STRINGFILE.get('links.docs.migrationGuide.responses'), sails.log.debug); sails.log.debug('Please define a "response" instead. (i.e. api/responses/notFound.js)'); sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)'); sails.log.debug('If you\'d like to use the default handler, just remove this configuration option.'); }); } }, /** * When this hook is loaded... */ initialize: function(cb) { // Register route syntax that allows explicit routes // to be bound directly to custom responses by name. // (e.g. {response: 'foo'}) sails.on('route:typeUnknown', onRoute(sails)); cb(); }, /** * Fetch relevant modules, exposing them on `sails` subglobal if necessary, */ loadModules: function(cb) { var hook = this; sails.log.verbose('Loading runtime custom response definitions...'); sails.modules.loadResponses(function loadedRuntimeErrorModules(err, responseDefs) { if (err) return cb(err); // Check that the user reserved response methods/properties var reservedResKeys = [ 'view', 'status', 'set', 'get', 'cookie', 'clearCookie', 'redirect', 'location', 'charset', 'send', 'json', 'jsonp', 'type', 'format', 'attachment', 'sendfile', 'download', 'links', 'locals', 'render' ]; _.each(Object.keys(responseDefs), function(userResponseKey) { if (_.contains(reservedResKeys, userResponseKey)) { return Err.invalidCustomResponse(userResponseKey); } }); // Ensure that required custom responses exist. _.defaults(responseDefs, { ok: require('./defaults/ok'), created: require('./defaults/created'), negotiate: require('./defaults/negotiate'), notFound: require('./defaults/notFound'), serverError: require('./defaults/serverError'), forbidden: require('./defaults/forbidden'), badRequest: require('./defaults/badRequest') }); // Register blueprint actions as middleware hook.middleware = responseDefs; return cb(); }); }, /** * Shadow route bindings * @type {Object} */ routes: { before: { /** * Add custom response methods to `res`. * * @param {Request} req * @param {Response} res * @param {Function} next [description] * @api private */ 'all /*': function addResponseMethods(req, res, next) { // Attach res.jsonx to `res` object _mixin_jsonx(req,res); // Attach custom responses to `res` object // Provide access to `req` and `res` in each of their `this` contexts. _.each(sails.middleware.responses, function eachMethod(responseFn, name) { res[name] = _.bind(responseFn, { req: req, res: res }); }); // Proceed! next(); } } } }; }; /** * [_mixin_jsonx description] * @param {[type]} req [description] * @param {[type]} res [description] * @return {[type]} [description] */ function _mixin_jsonx(req, res) { /** * res.jsonx(data) * * Serve JSON (and allow JSONP if enabled in `req.options`) * * @param {Object} data */ res.jsonx = res.jsonx || function jsonx (data){ // Send conventional status message if no data was provided // (see http://expressjs.com/api.html#res.send) if (_.isUndefined(data)) { return res.status(res.statusCode).send(); } else if (typeof data !== 'object') { // (note that this guard includes arrays) return res.send(data); } // When responding with an Error instance, if it's going to get sringified into // a dictionary with no `.stack` or `.message` properties, add them in. if (data instanceof Error) { var jsonSerializedErr; try { jsonSerializedErr = JSON.parse(JSON.stringify(data)); if (!jsonSerializedErr.stack || !jsonSerializedErr.message) { data = {message: data.message, stack: data.stack}; } } catch (e){ data = {message: data.message, stack: data.stack}; } } if ( req.options.jsonp && !req.isSocket ) { return res.jsonp(data); } else return res.json(data); }; } // Note for later // We could differentiate between 500 (generic error message) // and 504 (gateway did not receive response from upstream server) which could describe an IO problem // This is worth having a think about, since there are 2 fundamentally different kinds of "server errors": // (a) An infrastructural issue, or 504 (e.g. MySQL database randomly crashed or Twitter is down) // (b) Unexpected bug in app code, or 500 (e.g. `req.session.user.id`, but `req.session.user` doesn't exist)