UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

115 lines (100 loc) 4.54 kB
const { format, inspect } = require('../utils/cds-utils') /** * Constructs and optionally throws an Error object. * Usage variants: * * cds.error (404, 'code', 'message', { ... details }) * cds.error (404, { ... details }) * cds.error ({ ... any details }) * cds.error ('code', 'message', { ... details }) * cds.error ('message', { ... details }) * cds.error `template string usage variant` * cds.error (new Error, { ... any details }) * * When called with `new` the newly created Error is returned. * When called without `new` the error is thrown immediately. * The latter is useful for usages like that: * * let x = y || cds.error `expected y to be defined` * * @param {number} [status] - HTTP status code * @param {string} [code] - Stable error code, which clients can rely on * @param {string} [message] - Human-readable error message * @param {object} [details] - Additional error details * @param {Function} [caller] - The function calling us => stack trace cut off here * @returns {Error} The constructed Error (only when called with `new`) */ const error = exports = module.exports = function error ( status, code, msg, details, caller ) { let e = details if (status?.raw) [ msg, status, code, e, caller ] = [ error.message(...arguments) ] if (typeof status !== 'number') [ status, code, msg, e, caller ] = [ undefined, status, code, msg, e, caller ] if (typeof code === 'object') [ code, msg, e, caller ] = [ undefined, undefined, code, msg ] if (typeof msg === 'object') [ code, msg, e, caller ] = [ undefined, code, msg, e ] if (typeof e === 'string') [ status, msg, e, caller ] = [ msg, e, caller, error ] if (code && !msg) [ code, msg ] = [ undefined, code ] if (e instanceof Error || typeof e === 'object' && 'stack' in e) { // is error? e = Object.assign (e, caller) //> yes -> just decorate it } else { e = Object.assign (new Error (msg, e), e) Error.captureStackTrace (e, caller || error) } if (status) e.status = status if (code) e.code = code if (new.target) return e; else throw e } /** * Constructs a message from a tagged template string. In contrast to usual * template strings embedded values are formatted using `util.format` * not just `toString()`. * * let x = `A sample message with ${'a string'}, ${{an:'object'}}, and ${[1,2,3]}` * let y = cds.error.message`with ${'a string'}, ${{an:'object'}}, and ${[1,2,3]}` * //> x = A sample message with a string and [object Object], and 1,2,3 * //> y = with a string, { an: 'object' }, and [ 1, 2, 3 ] */ exports.message = (strings,...values) => { return String.raw(strings,...values.map(v => format(v))) } /** * Use that to construct and throw errors from a tagged template string * in validations of function arguments. * Use it like that: * * let x = {foo:'bar'} * typeof x === 'string' || cds.error.expected `${{x}} to be a string` * //> Error: Expected argument 'x' to be a string, but got: { foo: 'bar' } */ exports.expected = function expected ([,_to_be_expected], arg) { const [name] = Object.keys(arg), value = arg[name] return error (`Expected argument ${inspect(name)}${_to_be_expected}, but got: ${inspect(value)}`, undefined, expected) } exports.isSystemError = err => { // all errors thrown by the peggy parser or body-parser (used by express.json) should not crash the app if (err.name === 'SyntaxError' && (err.constructor?.name === 'peg$SyntaxError' || err.type === 'entity.parse.failed')) return false return err.name in { TypeError:1, ReferenceError:1, SyntaxError:1, RangeError:1, URIError:1, } } // // Private helpers ... // exports._duplicate_cds = (...locations) => { const { local } = require('../utils/cds-utils') throw error `Duplicate @sap/cds/common! There are duplicate versions of @sap/cds loaded from these locations: ${locations.map(local).join('\n ')} To fix this, check all dependencies to "@sap/cds" in your package.json and those of reused packages and ensure they allow deduped use of @sap/cds. ` } exports._no_primary_db = new Proxy ({},{ get: function fn(_,p) { error (`Not connected to primary datasource! Attempt to use 'cds.${p}' without prior connect to primary datasource, i.e. cds.connect.to('db'). ${ process.argv[1].endsWith('cds') && process.argv[2] in {run:1,serve:1} ? ` Please configure one thru 'cds.requires.db' or use in-memory db: cds ${process.argv[2]} --in-memory` : ''}` ,{},fn) }})