@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
115 lines (100 loc) • 4.54 kB
JavaScript
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) }})