UNPKG

ravel

Version:

Ravel Rapid Application Development Framework

140 lines (122 loc) 4.69 kB
'use strict'; const httpCodes = require('./http_codes'); /*! * Simplifies HTTP response codes and headers. Exposed as $Rest.respond below. * * @param {object} ravelInstance - A `ravel instance`. * @param {object} request - Koa.js request. * @param {object} response - Koa.js response. * @param {object} options - Any extra data, including: * - {(number | undefined)} okCode Desired 'success' response code (20x) to send if * there are no API errors. 200 OK by default for * GET/PUT/DELETE and 201 CREATED by default for POST * - {(number | undefined)} start * - {(number | undefined)} end * - {(number | undefined)} count * @private */ const buildRestResponse = function (ravelInstance, request, response, options) { options = options || {}; if (options.okCode === undefined) { if (response.body && request.method.toUpperCase() === 'POST') { options.okCode = httpCodes.CREATED; } else if (response.body) { options.okCode = httpCodes.OK; } else if (response.status >= 200 && response.status < 300) { // set no-content on this route if there's no body and no status is already set options.okCode = httpCodes.NO_CONTENT; } else { options.okCode = response.status; } } else if (options.okCode === httpCodes.PARTIAL_CONTENT && response.body !== undefined && options !== undefined && options.start !== undefined && options.end !== undefined && options.count !== undefined) { response.set('Content-Range', 'items ' + options.start + '-' + options.end + '/' + options.count); } if (options.okCode === httpCodes.CREATED && response.body && response.body.id && request.method.toUpperCase() === 'POST') { // try to set location header if we're creating something const resource = `${String(request.url)}/${String(response.body.id)}`; try { const url = new URL(resource, String(request.headers.origin)); response.set('Location', url.toString()); } catch (err) { // currently no good way to deal with relative URLs https://github.com/nodejs/node/issues/12682 response.set('Location', resource); } } response.status = options.okCode; }; const sRavelInstance = Symbol.for('_ravelInstance'); /** * Useful things related to REST, including the * automatic translation of Ravel.$err * errors into appropriate response codes. * * @private */ class Rest { /** * @param {Ravel} ravelInstance - A reference to a Ravel app instance. * @private */ constructor(ravelInstance) { this[sRavelInstance] = ravelInstance; Object.assign(this, require('./http_codes')); } /** * Syntactically simpler version of buildRestResponse * which is exposed to clients for callback-building. * * @returns {AsyncFunction} Koa middleware which will yield to user logic, catch thrown errors, * and respond with appropriate codes depending on the current verb or * error. Status can be configured using ctx.respondOptions.okCode. * @private */ respond() { return async (ctx, next) => { try { // overwrite ctx.status to be an alias for respondOptions.okCode // if it's already been overwritten, that's okay Object.defineProperty(ctx, 'status', { get: () => ctx.respondOptions ? ctx.respondOptions.okCode : undefined, set: newStatus => { if (ctx.respondOptions === undefined) { ctx.respondOptions = {}; } ctx.respondOptions.okCode = newStatus; } }); } finally { await next(); buildRestResponse(this[sRavelInstance], ctx.request, ctx.response, ctx.respondOptions); } }; } /** * Generic error-handling middleware. Catches exceptions and converts them * into appropriate HTTP status codes. * Automatically applied before all other middleware in Ravel. * * @private */ errorHandler() { return async (ctx, next) => { try { await next(); } catch (err) { // always overwrite body with error message ctx.response.type = 'text/plain; charset=utf-8'; if (err instanceof this[sRavelInstance].$err.General) { ctx.response.status = err.code; if (ctx.method !== 'HEAD') ctx.response.body = err.message; } else { this[sRavelInstance].$log.trace(err.stack); ctx.response.status = httpCodes.INTERNAL_SERVER_ERROR; if (ctx.method !== 'HEAD') ctx.response.body = err.stack; } } }; } } /*! * Export `Rest` */ module.exports = Rest;