@sap/cds 8.0.3 → 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/_i18n/i18n_bg.properties +113 -0
  3. package/_i18n/i18n_el.properties +113 -0
  4. package/_i18n/i18n_he.properties +113 -0
  5. package/_i18n/i18n_hr.properties +113 -0
  6. package/_i18n/i18n_kk.properties +113 -0
  7. package/_i18n/i18n_sh.properties +113 -0
  8. package/_i18n/i18n_sk.properties +113 -0
  9. package/_i18n/i18n_sl.properties +113 -0
  10. package/_i18n/i18n_uk.properties +113 -0
  11. package/lib/compile/etc/_localized.js +8 -20
  12. package/lib/dbs/cds-deploy.js +1 -0
  13. package/lib/env/cds-requires.js +1 -0
  14. package/lib/env/defaults.js +1 -1
  15. package/lib/env/plugins.js +22 -6
  16. package/lib/linked/validate.js +1 -1
  17. package/lib/log/cds-log.js +2 -2
  18. package/lib/srv/protocols/hcql.js +5 -5
  19. package/lib/srv/protocols/http.js +23 -11
  20. package/lib/test/expect.js +1 -1
  21. package/lib/utils/cds-test.js +4 -4
  22. package/libx/_runtime/common/composition/insert.js +1 -1
  23. package/libx/_runtime/common/error/utils.js +2 -1
  24. package/libx/_runtime/common/generic/input.js +2 -5
  25. package/libx/_runtime/common/generic/stream.js +18 -3
  26. package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
  27. package/libx/_runtime/db/query/read.js +18 -9
  28. package/libx/_runtime/fiori/lean-draft.js +2 -2
  29. package/libx/_runtime/hana/customBuilder/CustomReferenceBuilder.js +1 -1
  30. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +2 -2
  31. package/libx/_runtime/messaging/event-broker.js +76 -24
  32. package/libx/common/assert/utils.js +1 -57
  33. package/libx/odata/middleware/batch.js +86 -40
  34. package/libx/odata/middleware/body-parser.js +2 -3
  35. package/libx/odata/middleware/operation.js +11 -11
  36. package/libx/odata/middleware/read.js +1 -1
  37. package/libx/odata/parse/grammar.peggy +6 -1
  38. package/libx/odata/parse/parser.js +1 -1
  39. package/libx/odata/utils/metadata.js +18 -44
  40. package/libx/rest/middleware/error.js +9 -2
  41. package/libx/rest/middleware/parse.js +1 -1
  42. package/package.json +1 -1
  43. package/libx/common/assert/index.js +0 -228
  44. 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
- }