@sap/cds 8.0.4 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/_i18n/i18n_bg.properties +113 -0
- package/_i18n/i18n_el.properties +113 -0
- package/_i18n/i18n_he.properties +113 -0
- package/_i18n/i18n_hr.properties +113 -0
- package/_i18n/i18n_kk.properties +113 -0
- package/_i18n/i18n_sh.properties +113 -0
- package/_i18n/i18n_sk.properties +113 -0
- package/_i18n/i18n_sl.properties +113 -0
- package/_i18n/i18n_uk.properties +113 -0
- package/lib/compile/etc/_localized.js +8 -20
- package/lib/dbs/cds-deploy.js +1 -0
- package/lib/env/defaults.js +1 -1
- package/lib/env/plugins.js +22 -6
- package/lib/linked/validate.js +1 -1
- package/lib/log/cds-log.js +2 -2
- package/lib/srv/protocols/hcql.js +5 -5
- package/lib/srv/protocols/http.js +23 -11
- package/lib/test/expect.js +1 -1
- package/lib/utils/cds-test.js +4 -4
- package/libx/_runtime/common/error/utils.js +2 -1
- package/libx/_runtime/common/generic/input.js +2 -5
- package/libx/_runtime/common/generic/stream.js +18 -3
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/fiori/lean-draft.js +1 -1
- package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +1 -1
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
- package/libx/_runtime/messaging/event-broker.js +23 -9
- package/libx/common/assert/utils.js +1 -57
- package/libx/odata/middleware/batch.js +5 -6
- package/libx/odata/middleware/body-parser.js +2 -3
- package/libx/odata/middleware/operation.js +11 -11
- package/libx/odata/parse/grammar.peggy +6 -1
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/metadata.js +18 -44
- package/libx/rest/middleware/parse.js +1 -1
- package/package.json +1 -1
- package/libx/common/assert/index.js +0 -228
- package/libx/common/assert/type-relaxed.js +0 -39
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
const { cds } = global
|
|
2
|
-
|
|
3
|
-
const strictTypeCheckers = require('./type-strict')
|
|
4
|
-
const relaxedTypeCheckers = require('./type-relaxed')
|
|
5
|
-
let typeCheckers
|
|
6
|
-
|
|
7
|
-
const { checkMandatory, checkEnum, checkRange, checkFormat } = require('./validation')
|
|
8
|
-
const { getNested, getTarget, resolveCDSType, resolveSegment } = require('./utils')
|
|
9
|
-
|
|
10
|
-
const _no_op = () => {}
|
|
11
|
-
|
|
12
|
-
const _reject_unknown = (_, k, def, errs) =>
|
|
13
|
-
errs.push(new cds.error(`Property "${k}" does not exist in ${def.name}`, { statusCode: 400, code: '400' }))
|
|
14
|
-
|
|
15
|
-
const _filter_unknown = (obj, k) => delete obj[k]
|
|
16
|
-
|
|
17
|
-
const _handle_mandatories = (obj, def, errs, path) => {
|
|
18
|
-
for (const [k, ele] of def._mandatories) {
|
|
19
|
-
const v = obj[k] === undefined ? getNested(k, obj) : obj[k]
|
|
20
|
-
checkMandatory(v, ele, errs, path, k)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const _handle_mandatories_if_insert = (obj, def, errs, path) => {
|
|
25
|
-
if (!def.keys) return _handle_mandatories(obj, def, errs, path)
|
|
26
|
-
const allKeysProvided = Object.keys(def.keys).every(k => k in obj)
|
|
27
|
-
for (const [k, ele] of def._mandatories) {
|
|
28
|
-
const v = obj[k] === undefined ? getNested(k, obj) : obj[k]
|
|
29
|
-
if (!allKeysProvided || v !== undefined) checkMandatory(v, ele, errs, path, k)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function _recurse(obj, prefix, def, errs, opts) {
|
|
34
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
35
|
-
if (v != null && typeof v === 'object' && !Array.isArray(v)) {
|
|
36
|
-
_recurse(v, prefix + k + '_', def, errs, opts)
|
|
37
|
-
continue
|
|
38
|
-
}
|
|
39
|
-
const flat = { [prefix + k]: v }
|
|
40
|
-
_process(flat, def, errs, opts)
|
|
41
|
-
if (!Object.keys(flat).length) delete obj[k] //> filtered out inside _process -> propagate to original object
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function _process(obj, def, errs, opts) {
|
|
46
|
-
if (obj == null) return
|
|
47
|
-
|
|
48
|
-
if (Array.isArray(obj)) {
|
|
49
|
-
for (const row of obj) _process(row, def, errs, opts)
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// TODO: path should be cqn
|
|
54
|
-
const prev = opts.path.length && opts.path[opts.path.length - 1]
|
|
55
|
-
if (prev?.keys || prev?.index) opts.path[opts.path.length - 1] = resolveSegment(prev, obj, def)
|
|
56
|
-
|
|
57
|
-
if (def._mandatories?.length) opts._handle_mandatories(obj, def, errs, opts.path)
|
|
58
|
-
|
|
59
|
-
for (let [k, v] of Object.entries(obj)) {
|
|
60
|
-
let ele = def.elements?.[k] || def.params?.[k] || def.items
|
|
61
|
-
if (typeof ele !== 'object') ele = undefined //> ignore non-object elements, e.g., functions of prototypes
|
|
62
|
-
|
|
63
|
-
/*
|
|
64
|
-
* TODO: should we support this? with or without transformation?
|
|
65
|
-
* structured vs flat
|
|
66
|
-
* the combination of the two cases below SHOULD cover mixed cases like
|
|
67
|
-
* foo: { bar: { baz: { ... } } } and foo_bar: { baz: { ... } }
|
|
68
|
-
* TODO: add tests!!!
|
|
69
|
-
*/
|
|
70
|
-
// case 1: structured data but flat model
|
|
71
|
-
if (
|
|
72
|
-
!ele &&
|
|
73
|
-
typeof obj[k] === 'object' &&
|
|
74
|
-
!Array.isArray(obj[k]) &&
|
|
75
|
-
(def.elements || def.params) &&
|
|
76
|
-
Object.keys(def.elements || def.params).find(key => key.startsWith(`${k}_`))
|
|
77
|
-
) {
|
|
78
|
-
_recurse(obj[k], k + '_', def, errs, opts)
|
|
79
|
-
continue
|
|
80
|
-
}
|
|
81
|
-
// case 2: flat data but structured model
|
|
82
|
-
if (!ele && k.split('_').length > 1) {
|
|
83
|
-
// TODO: handle stuff like foo__bar, i.e., foo_: { bar: ... }
|
|
84
|
-
const parts = k.split('_')
|
|
85
|
-
let cur = def.elements || def.params
|
|
86
|
-
while (cur && parts.length) cur = (cur.elements || cur.params)?.[parts.shift()]
|
|
87
|
-
if (cur) ele = cur
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (!ele) {
|
|
91
|
-
if (!def['@open']) opts._handle_unknown(obj, k, def, errs)
|
|
92
|
-
continue
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (ele['@cds.api.ignore']) {
|
|
96
|
-
opts._handle_unknown(obj, k, def, errs)
|
|
97
|
-
continue
|
|
98
|
-
}
|
|
99
|
-
if (ele.isAssociation) {
|
|
100
|
-
const keys = ele.keys?.map(k => k.ref[0]) || Object.keys(ele._target.keys)
|
|
101
|
-
opts.path.push(ele.is2many || Object.keys(keys).length ? { assoc: k, keys } : k)
|
|
102
|
-
// NOTE: the assumption is that children with all keys provided are not inserted, but updated
|
|
103
|
-
// -> incomplete but best we can do without roundtrip
|
|
104
|
-
_process(v, ele._target, errs, {
|
|
105
|
-
...opts,
|
|
106
|
-
_handle_mandatories:
|
|
107
|
-
opts.mandatories === false || ele._isAssociationStrict
|
|
108
|
-
? _no_op
|
|
109
|
-
: opts.mandatories === true
|
|
110
|
-
? _handle_mandatories
|
|
111
|
-
: _handle_mandatories_if_insert
|
|
112
|
-
})
|
|
113
|
-
opts.path.pop()
|
|
114
|
-
continue
|
|
115
|
-
}
|
|
116
|
-
if (ele._isStructured) {
|
|
117
|
-
opts.path.push(k)
|
|
118
|
-
_process(v, ele, errs, opts)
|
|
119
|
-
opts.path.pop()
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
if (ele instanceof cds.builtin.classes.array && v !== undefined) {
|
|
123
|
-
if (!Array.isArray(v)) {
|
|
124
|
-
// REVISIT: allow null if served via REST or OData v4.02
|
|
125
|
-
const target = getTarget(opts.path, k)
|
|
126
|
-
errs.push(new cds.error('ASSERT_ARRAY', { target, statusCode: 400, code: '400' }))
|
|
127
|
-
continue
|
|
128
|
-
}
|
|
129
|
-
for (let i = 0; i < v.length; i++) {
|
|
130
|
-
opts.path.push({ prop: k, index: i })
|
|
131
|
-
const _def = ele.items?.__proto__.elements ? ele.items.__proto__ : ele.__proto__
|
|
132
|
-
const _obj = _def.elements ? v[i] : { [k]: v[i] }
|
|
133
|
-
_process(_obj, _def, errs, opts)
|
|
134
|
-
opts.path.pop()
|
|
135
|
-
}
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (ele.notNull && v === null) {
|
|
140
|
-
const target = getTarget(opts.path, k)
|
|
141
|
-
errs.push(new cds.error('ASSERT_NOT_NULL', { target, statusCode: 400, code: '400' }))
|
|
142
|
-
continue
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const type = resolveCDSType(ele)
|
|
146
|
-
if (type?.match(/^cds\.hana\./)) continue
|
|
147
|
-
|
|
148
|
-
let typeChecker = typeCheckers[type]
|
|
149
|
-
if (typeChecker) {
|
|
150
|
-
if (v == null) continue
|
|
151
|
-
|
|
152
|
-
// type check
|
|
153
|
-
// REVISIT: all checkers should add errors themselves!
|
|
154
|
-
if (type === 'cds.Decimal' && opts.strict) {
|
|
155
|
-
typeChecker(v, ele, errs, opts.path, k) //> _checkDecimal adds error itself
|
|
156
|
-
} else if (!typeChecker(v, ele)) {
|
|
157
|
-
errs.push(
|
|
158
|
-
new cds.error('ASSERT_DATA_TYPE', {
|
|
159
|
-
args: [typeof v === 'string' ? `"${v}"` : v, ele._type],
|
|
160
|
-
target: getTarget(opts.path, k),
|
|
161
|
-
statusCode: 400,
|
|
162
|
-
code: '400'
|
|
163
|
-
})
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// propagate correction if necessary
|
|
168
|
-
if (obj[k] !== v) obj[k] = v
|
|
169
|
-
|
|
170
|
-
// @assert
|
|
171
|
-
if (ele['@assert.range'] && ele.enum) checkEnum(v, ele, errs, opts.path, k)
|
|
172
|
-
if (ele['@assert.range']) checkRange(v, ele, errs, opts.path, k)
|
|
173
|
-
if (ele['@assert.format']) checkFormat(v, ele, errs, opts.path, k)
|
|
174
|
-
// REVISIT: @assert.target? -> no because async, but maybe return the necessary query to execute?
|
|
175
|
-
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
throw new Error(`Missing type check for "${ele.type}" (property "${k}" of "${def.name}")`)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Asserts the given data against the given CSN definition and returns an array of errors or undefined.
|
|
185
|
-
*
|
|
186
|
-
* @param {object} data - the data to be checked
|
|
187
|
-
* @param {LinkedCSN} definition - the CSN definition to which the data should be checked against
|
|
188
|
-
* @param {object} [options] - options
|
|
189
|
-
* @param {boolean} [options.strict] - if true, an error is thrown if a property is not defined in the CSN
|
|
190
|
-
* @param {boolean} [options.filter] - if true, properties not defined in the CSN are filtered out
|
|
191
|
-
* @param {boolean} [options.mandatories] - if false, mandatory properties are never checked.
|
|
192
|
-
* if true, mandatory properties are always checked.
|
|
193
|
-
* if undefined, mandatory properties are checked for presumed insert rows only (determined by a heuristic to avoid roundtrip).
|
|
194
|
-
* @param {object} [options.http] - the HTTP request object providing access to headers, etc.
|
|
195
|
-
* @param {*[]} [options.path] - collector for the current path, should not be set manually
|
|
196
|
-
* @return {Array} - an array of errors or undefined if no errors
|
|
197
|
-
*/
|
|
198
|
-
module.exports = (data, definition, options = {}) => {
|
|
199
|
-
if (!data) throw new Error('Argument "data" was not provided')
|
|
200
|
-
if (typeof data !== 'object') throw new Error('Argument "data" must be an object (or an array)')
|
|
201
|
-
|
|
202
|
-
if (!definition) throw new Error('Argument "entity" was not provided')
|
|
203
|
-
// FIXME: definition instanceof cds.builtin.classes.any doesn't always work for some reason
|
|
204
|
-
if (!(definition instanceof cds.builtin.classes.any) && !(definition.kind in { entity: 1, action: 1, function: 1 })) {
|
|
205
|
-
throw new Error('Argument "definition" is not a valid CSN element')
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// TODO: feature flags instead of process env vars
|
|
209
|
-
options.strict ??= process.env.CDS_ASSERT_STRICT === 'true'
|
|
210
|
-
options.filter ??= process.env.CDS_ASSERT_FILTER === 'true'
|
|
211
|
-
options.path ??= []
|
|
212
|
-
|
|
213
|
-
// materialize what is done ...
|
|
214
|
-
// ... re type checks
|
|
215
|
-
typeCheckers = options.strict ? strictTypeCheckers : relaxedTypeCheckers
|
|
216
|
-
// ... in case of unknown elements
|
|
217
|
-
if (options.strict) options._handle_unknown = _reject_unknown
|
|
218
|
-
else if (options.filter) options._handle_unknown = _filter_unknown
|
|
219
|
-
else options._handle_unknown = _no_op
|
|
220
|
-
// ... regarding mandatory elements
|
|
221
|
-
if (options.mandatories === false) options._handle_mandatories = _no_op
|
|
222
|
-
else if (options.mandatories === true) options._handle_mandatories = _handle_mandatories
|
|
223
|
-
else options._handle_mandatories = _handle_mandatories_if_insert
|
|
224
|
-
|
|
225
|
-
const errs = []
|
|
226
|
-
_process(data, definition, errs, options)
|
|
227
|
-
return errs.length ? errs : undefined
|
|
228
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const { Readable } = require('stream')
|
|
2
|
-
|
|
3
|
-
const _isString = v => typeof v === 'string'
|
|
4
|
-
|
|
5
|
-
const _isBoolean = v => typeof v === 'boolean'
|
|
6
|
-
|
|
7
|
-
const _isNumber = v => typeof v === 'number'
|
|
8
|
-
|
|
9
|
-
const _isNumberish = v => !!Number(v) || v === 0 //> string representation of number is ok, e.g., '1e3'
|
|
10
|
-
|
|
11
|
-
const _isDate = v => !!Date.parse(v)
|
|
12
|
-
|
|
13
|
-
const _isTime = v => !!Date.parse('2000-01-01T' + v)
|
|
14
|
-
|
|
15
|
-
const _isBuffer = v => Buffer.isBuffer(v) || v.type === 'Buffer' || _isString(v) //> base64 encoded string is ok as well
|
|
16
|
-
|
|
17
|
-
const _isStreamOrBuffer = v => v instanceof Readable || _isBuffer(v)
|
|
18
|
-
|
|
19
|
-
module.exports = {
|
|
20
|
-
'cds.UUID': _isString,
|
|
21
|
-
'cds.Boolean': _isBoolean,
|
|
22
|
-
'cds.Integer': _isNumber,
|
|
23
|
-
'cds.UInt8': _isNumber,
|
|
24
|
-
'cds.Int16': _isNumber,
|
|
25
|
-
'cds.Int32': _isNumber,
|
|
26
|
-
'cds.Integer64': _isNumberish,
|
|
27
|
-
'cds.Int64': _isNumberish,
|
|
28
|
-
'cds.Decimal': _isNumberish,
|
|
29
|
-
'cds.DecimalFloat': _isNumber,
|
|
30
|
-
'cds.Double': _isNumber,
|
|
31
|
-
'cds.Date': _isDate,
|
|
32
|
-
'cds.Time': _isTime,
|
|
33
|
-
'cds.DateTime': _isDate,
|
|
34
|
-
'cds.Timestamp': _isDate,
|
|
35
|
-
'cds.String': _isString,
|
|
36
|
-
'cds.Binary': _isBuffer,
|
|
37
|
-
'cds.LargeString': _isString,
|
|
38
|
-
'cds.LargeBinary': _isStreamOrBuffer
|
|
39
|
-
}
|