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