@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
110 lines (93 loc) • 3.91 kB
JavaScript
const { cds } = global
const {
'cds.Date': checkISODate,
'cds.Time': checkISOTime,
'cds.DateTime': checkISODateTime,
'cds.Timestamp': checkISOTimestamp,
'cds.String': checkString
} = require('./type-strict')
const { getTarget, resolveCDSType } = require('./utils')
const _isNavigationColumn = (col, as) => col.ref?.length > 1 && (col.as === as || col.ref[col.ref.length - 1] === as)
// REVISIT: mandatory is actually not the same as not null or empty string
const _isNotFilled = val => val === null || val === undefined || (typeof val === 'string' && val.trim() === '')
const _getEnumElement = ele => ((ele['@assert.range'] && ele.enum) ? ele.enum : undefined)
const _enumValues = ele => {
return Object.keys(ele).map(enumKey => {
const enum_ = ele[enumKey]
const enumValue = enum_ && enum_.val
if (enumValue !== undefined) {
if (enumValue['=']) return enumValue['=']
if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
return enumValue
}
return enumKey
})
}
const _checkDateValue = (val, r1, r2) => {
const dateVal = new Date(val)
return (dateVal - new Date(r1)) * (dateVal - new Date(r2)) <= 0
}
const _toDate = val => `2000-01-01T${val}Z`
const _checkInRange = (val, range, type) => {
switch (type) {
case 'cds.Date':
return checkISODate(val) && _checkDateValue(val, range[0], range[1])
case 'cds.DateTime':
return checkISODateTime(val) && _checkDateValue(val, range[0], range[1])
case 'cds.Timestamp':
return checkISOTimestamp(val) && _checkDateValue(val, range[0], range[1])
case 'cds.Time':
return checkISOTime(val) && _checkDateValue(_toDate(val), _toDate(range[0]), _toDate(range[1]))
default:
return (val - range[0]) * (val - range[1]) <= 0
}
}
// process.env.CDS_ASSERT_FORMAT_FLAGS is not official!
const _checkRegExpFormat = (val, format) =>
checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
const checkMandatory = (v, ele, errs, path, k) => {
// REVISIT: correct to not complain?
// do not complain about missing foreign keys in children
if (path.length && ele['@odata.foreignKey4']) return
// TODO: which case is this?
// do not complain about ???
if (ele.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, ele.name))) return
if (_isNotFilled(v)) {
const target = getTarget(path, k)
errs.push(new cds.error('ASSERT_NOT_NULL', { target, statusCode: 400, code: '400' }))
}
}
const checkEnum = (v, ele, errs, path, k) => {
const enumElements = _getEnumElement(ele)
const enumValues = enumElements && _enumValues(enumElements)
if (enumElements && !enumValues.some(ev => ev == v)) { //> use == for automatic type coercion
const args =
typeof v === 'string'
? ['"' + v + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
: [v, enumValues.join(', ')]
const target = getTarget(path, k)
errs.push(new cds.error('ASSERT_ENUM', { args, target, statusCode: 400, code: '400' }))
}
}
const checkRange = (v, ele, errs, path, k) => {
const rangeElements = ele['@assert.range'] && !_getEnumElement(ele) ? ele['@assert.range'] : undefined
if (rangeElements && !_checkInRange(v, rangeElements, resolveCDSType(ele))) {
const args = [v, ...ele['@assert.range']]
const target = getTarget(path, k)
errs.push(new cds.error('ASSERT_RANGE', { args, target, statusCode: 400, code: '400' }))
}
}
const checkFormat = (v, ele, errs, path, k) => {
const formatElements = ele['@assert.format']
if (formatElements && !_checkRegExpFormat(v, formatElements)) {
const args = [v, formatElements]
const target = getTarget(path, k)
errs.push(new cds.error('ASSERT_FORMAT', { args, target, statusCode: 400, code: '400' }))
}
}
module.exports = {
checkMandatory,
checkEnum,
checkRange,
checkFormat
}