UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

107 lines (92 loc) 4.02 kB
const cds = require('../../../lib') const { shutdown_on_uncaught_errors } = cds.env.server exports = module.exports = () => function odata_error(err, req, res, next) { if (exports.pass_through(err)) return next(err) else req._is_odata = true if (err.details) err = _fioritized(err) exports.normalizeError(err, req) return next(err) } exports.pass_through = err => { if (err == 401 || err.code == 401) return true if (shutdown_on_uncaught_errors && !(err.status || err.statusCode) && cds.error.isSystemError(err)) return true } exports.normalizeError = (err, req, cleanse = ODATA_PROPERTIES) => { err.status = _normalize(err, req, cleanse) || 500 return err } exports.getLocalizedMessages = (messages, req) => { const locale = cds.i18n.locale.from(req) for (let m of messages) _normalize(m, req, SAP_MSG_PROPERTIES, locale) return messages } exports.getSapMessages = (messages, req) => { messages = exports.getLocalizedMessages(messages, req) return JSON.stringify(messages).replace( /[\u007F-\uFFFF]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0') ) } const { i18n } = require('../../../lib') const ODATA_PROPERTIES = { code: 1, message: 1, target: 1, details: 1, innererror: 1 } const SAP_MSG_PROPERTIES = { ...ODATA_PROPERTIES, longtextUrl: 2, transition: 2, numericSeverity: 2 } const BAD_REQUESTS = { ENTITY_ALREADY_EXISTS: 1, FK_CONSTRAINT_VIOLATION: 2, UNIQUE_CONSTRAINT_VIOLATION: 3 } // prettier-ignore const _normalize = (err, req, keep, locale = cds.i18n.locale.from(req) || cds.env.i18n.default_language, content_id = req.get('content-id') ) => { // Determine status code if not already set const details = err.details?.map?.(each => _normalize (each, req, keep, locale, content_id)) const status = err.status || err.statusCode || _status4(err) || _reduce(details) // Determine error code and message const key = err.message || err.code || status const msg = i18n.messages.at (key, '', err.args) // lookup messages for log output from factory default texts if (msg && msg !== key) { if (typeof err.code !== 'string') err.code = key err.message = msg } if (typeof err.code !== 'string') err.code = String(err.code ?? status ?? '') // Cleanse and localize in response to client Object.defineProperty(err, 'toJSON', { value: function() { const that = keep ? {} : {...this} if (keep) for (let k in this) if (k in keep || k[0] === '@') that[k] = this[k] if (req._is_odata && keep !== SAP_MSG_PROPERTIES) { that['@Common.numericSeverity'] ??= err.numericSeverity || 4 if (content_id) that['@Core.ContentID'] = content_id } if (locale) that.message = _message4 (err, key, locale) if (!that.message) that.message = this.message return that }}) return status } const _status4 = err => { if (err.code >= 300 && err.code < 600 && !err.sqlState) return Number(err.code) if (err.message in BAD_REQUESTS) return 400 // REVISIT: should we use 409 or 500 instead? } const _message4 = (err, key, locale) => { if (err.i18n) { key = /{i18n>(.*)}/.exec(err.i18n)?.[1] if (!key) return err.i18n } return i18n.messages.at(key, locale, err.args) } const _reduce = details => { const unique = [...new Set(details)] if (unique.length === 1) return unique[0] // if only one unique status exists, we use that if (unique.some(s => s >= 500)) return 500 // if at least one 5xx exists, we use 500 if (unique.some(s => s >= 400)) return 400 // if at least one 4xx exists, we use 400 } /** * According to the Fiori Elements Failed Message specification, the format must be: * Root level: First error, Details: Other errors */ // prettier-ignore const _fioritized = cds.env.fiori.wrap_multiple_errors === false ? err => { const [ head, ...tail ] = err.details return Object.assign (err, head, { details: tail.length ? tail : undefined }) } : err => { err.target ??= 'in' // prevents annoying 'Multiple Errors' modal popups in Fiori return err }