@sap/cds 7.6.3 → 7.7.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 +38 -1
- package/_i18n/i18n.properties +3 -0
- package/app/index.js +18 -12
- package/bin/serve.js +51 -19
- package/common.cds +16 -0
- package/lib/auth/ias-auth.js +2 -2
- package/lib/auth/index.js +1 -1
- package/lib/auth/jwt-auth.js +1 -1
- package/lib/compile/cdsc.js +23 -11
- package/lib/compile/for/nodejs.js +2 -2
- package/lib/compile/for/odata.js +4 -0
- package/lib/compile/load.js +7 -2
- package/lib/compile/to/sql.js +3 -0
- package/lib/dbs/cds-deploy.js +197 -220
- package/lib/env/defaults.js +2 -1
- package/lib/index.js +8 -2
- package/lib/linked/types.js +1 -0
- package/lib/log/format/json.js +1 -1
- package/lib/plugins.js +2 -2
- package/lib/ql/Query.js +1 -1
- package/lib/ql/SELECT.js +8 -8
- package/lib/req/context.js +22 -13
- package/lib/req/request.js +10 -4
- package/lib/srv/cds-connect.js +9 -3
- package/lib/srv/cds-serve.js +5 -3
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/protocols/odata-v4.js +38 -9
- package/lib/srv/srv-api.js +98 -140
- package/lib/srv/srv-models.js +2 -2
- package/lib/srv/srv-tx.js +1 -0
- package/lib/utils/cds-utils.js +32 -23
- package/lib/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +18 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +7 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriParser.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/http/HttpHeaderReader.js +4 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/index.js +5 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +71 -25
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +10 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +6 -1
- package/libx/_runtime/cds-services/util/assert.js +50 -240
- package/libx/_runtime/cds.js +5 -0
- package/libx/_runtime/common/aspects/any.js +53 -45
- package/libx/_runtime/common/generic/input.js +14 -10
- package/libx/_runtime/common/generic/paging.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/quotingStyles.js +1 -1
- package/libx/_runtime/common/utils/resolveStructured.js +4 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +5 -12
- package/libx/_runtime/common/utils/stream.js +2 -16
- package/libx/_runtime/common/utils/streamProp.js +16 -6
- package/libx/_runtime/common/utils/ucsn.js +1 -0
- package/libx/_runtime/db/utils/columns.js +6 -1
- package/libx/_runtime/fiori/generic/activate.js +11 -3
- package/libx/_runtime/fiori/generic/edit.js +8 -2
- package/libx/_runtime/fiori/lean-draft.js +99 -30
- package/libx/_runtime/hana/execute.js +2 -5
- package/libx/_runtime/messaging/service.js +6 -2
- package/libx/common/assert/index.js +232 -0
- package/libx/common/assert/type.js +109 -0
- package/libx/common/assert/utils.js +125 -0
- package/libx/common/assert/validation.js +109 -0
- package/libx/odata/index.js +5 -5
- package/libx/odata/middleware/create.js +83 -0
- package/libx/odata/middleware/delete.js +38 -0
- package/libx/odata/middleware/error.js +8 -0
- package/libx/odata/{metadata.js → middleware/metadata.js} +8 -6
- package/libx/odata/middleware/operation.js +78 -0
- package/libx/odata/middleware/parse.js +11 -0
- package/libx/odata/{read.js → middleware/read.js} +42 -20
- package/libx/odata/{service-document.js → middleware/service-document.js} +2 -1
- package/libx/odata/middleware/stream.js +237 -0
- package/libx/odata/middleware/update.js +165 -0
- package/libx/odata/{afterburner.js → parse/afterburner.js} +79 -29
- package/libx/odata/{cqn2odata.js → parse/cqn2odata.js} +5 -3
- package/libx/odata/{parseToCqn.js → parse/parseToCqn.js} +3 -6
- package/libx/odata/{utils.js → utils/index.js} +91 -9
- package/libx/outbox/index.js +5 -4
- package/libx/rest/RestAdapter.js +0 -1
- package/libx/rest/middleware/operation.js +6 -4
- package/libx/rest/middleware/parse.js +20 -2
- package/package.json +1 -1
- package/server.js +43 -71
- package/libx/odata/create.js +0 -44
- package/libx/odata/delete.js +0 -25
- package/libx/odata/error.js +0 -12
- package/libx/odata/update.js +0 -110
- /package/libx/odata/{grammar.peggy → parse/grammar.peggy} +0 -0
- /package/libx/odata/{parser.js → parse/parser.js} +0 -0
- /package/libx/odata/{result.js → utils/result.js} +0 -0
|
@@ -5,20 +5,9 @@ const templatePathSerializer = require('../../common/utils/templateProcessorPath
|
|
|
5
5
|
// REVISIT: replace with cds.Request
|
|
6
6
|
const getEntry = require('../../common/error/entry')
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const ISO_DATE = `(?:${ISO_DATE_PART1}|${ISO_DATE_PART2})`
|
|
12
|
-
const ISO_TIME_NO_MILLIS = '(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d'
|
|
13
|
-
const ISO_TIME = `${ISO_TIME_NO_MILLIS}(?:\\.\\d{1,9})?`
|
|
14
|
-
const ISO_DATE_TIME = `${ISO_DATE}T${ISO_TIME_NO_MILLIS}(?:Z|[+-][01]\\d:?[0-5]\\d)`
|
|
15
|
-
const ISO_TIMESTAMP = `${ISO_DATE}T${ISO_TIME}(?:Z|[+-][01]\\d:?[0-5]\\d)`
|
|
16
|
-
|
|
17
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
18
|
-
const ISO_DATE_REGEX = new RegExp(`^${ISO_DATE}$`, 'i')
|
|
19
|
-
const ISO_TIME_REGEX = new RegExp(`^${ISO_TIME_NO_MILLIS}$`, 'i')
|
|
20
|
-
const ISO_DATE_TIME_REGEX = new RegExp(`^${ISO_DATE_TIME}$`, 'i')
|
|
21
|
-
const ISO_TIMESTAMP_REGEX = new RegExp(`^${ISO_TIMESTAMP}$`, 'i')
|
|
8
|
+
const typeCheckers = require('../../../common/assert/type')
|
|
9
|
+
const { 'cds.Decimal': checkDecimal } = typeCheckers
|
|
10
|
+
const { checkMandatory, checkEnum, checkRange, checkFormat } = require('../../../common/assert/validation')
|
|
22
11
|
|
|
23
12
|
const ASSERT_VALID_ELEMENT = 'ASSERT_VALID_ELEMENT'
|
|
24
13
|
const ASSERT_RANGE = 'ASSERT_RANGE'
|
|
@@ -80,93 +69,6 @@ const assertError = (code, element, value, key, path) => {
|
|
|
80
69
|
return assertError
|
|
81
70
|
}
|
|
82
71
|
|
|
83
|
-
const _checkString = value => typeof value === 'string'
|
|
84
|
-
|
|
85
|
-
const _checkNumber = value => typeof value === 'number'
|
|
86
|
-
|
|
87
|
-
const _checkDecimal = (value, element) => {
|
|
88
|
-
const [left, right] = String(value).split('.')
|
|
89
|
-
return (
|
|
90
|
-
_checkNumber(value) &&
|
|
91
|
-
(!element.precision || left.length <= element.precision - (element.scale || 0)) &&
|
|
92
|
-
(!element.scale || ((right || '').length <= element.scale && parseFloat(right) !== 0))
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const _checkInteger = value => _checkNumber(value) && parseInt(value, 10) === value
|
|
97
|
-
|
|
98
|
-
const _checkBoolean = value => typeof value === 'boolean'
|
|
99
|
-
|
|
100
|
-
// REVISIT: Extension parameter in push is an object with buffer data
|
|
101
|
-
const _checkBuffer = value => Buffer.isBuffer(value) || value.type === 'Buffer'
|
|
102
|
-
|
|
103
|
-
const _checkUUID = value => {
|
|
104
|
-
return _checkString(value) && UUID_REGEX.test(value)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const _checkISODate = value => (_checkString(value) && ISO_DATE_REGEX.test(value)) || value instanceof Date
|
|
108
|
-
|
|
109
|
-
const _checkISOTime = value => _checkString(value) && ISO_TIME_REGEX.test(value)
|
|
110
|
-
|
|
111
|
-
const _checkISODateTime = value => (_checkString(value) && ISO_DATE_TIME_REGEX.test(value)) || value instanceof Date
|
|
112
|
-
|
|
113
|
-
const _checkISOTimestamp = value => (_checkString(value) && ISO_TIMESTAMP_REGEX.test(value)) || value instanceof Date
|
|
114
|
-
|
|
115
|
-
const _checkDateValue = (val, r1, r2) => {
|
|
116
|
-
const dateVal = new Date(val)
|
|
117
|
-
return (dateVal - new Date(r1)) * (dateVal - new Date(r2)) <= 0
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const _toDate = val => `2000-01-01T${val}Z`
|
|
121
|
-
|
|
122
|
-
const _checkInRange = (val, range, type) => {
|
|
123
|
-
switch (type) {
|
|
124
|
-
case 'cds.Date':
|
|
125
|
-
return _checkISODate(val) && _checkDateValue(val, range[0], range[1])
|
|
126
|
-
case 'cds.DateTime':
|
|
127
|
-
return _checkISODateTime(val) && _checkDateValue(val, range[0], range[1])
|
|
128
|
-
case 'cds.Timestamp':
|
|
129
|
-
return _checkISOTimestamp(val) && _checkDateValue(val, range[0], range[1])
|
|
130
|
-
case 'cds.Time':
|
|
131
|
-
return _checkISOTime(val) && _checkDateValue(_toDate(val), _toDate(range[0]), _toDate(range[1]))
|
|
132
|
-
default:
|
|
133
|
-
return (val - range[0]) * (val - range[1]) <= 0
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const _resolveCDSType = element => {
|
|
138
|
-
if (element.type.startsWith('cds.')) return element.type
|
|
139
|
-
if (!element.type) return
|
|
140
|
-
|
|
141
|
-
return _resolveCDSType(element.__proto__)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// process.env.CDS_ASSERT_FORMAT_FLAGS not official!
|
|
145
|
-
const _checkRegExpFormat = (val, format) =>
|
|
146
|
-
_checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
|
|
147
|
-
|
|
148
|
-
const CDS_TYPE_CHECKS = {
|
|
149
|
-
'cds.UUID': _checkUUID,
|
|
150
|
-
'cds.Boolean': _checkBoolean,
|
|
151
|
-
'cds.Integer': _checkInteger,
|
|
152
|
-
'cds.UInt8': _checkInteger,
|
|
153
|
-
'cds.Int16': _checkInteger,
|
|
154
|
-
'cds.Int32': _checkInteger,
|
|
155
|
-
'cds.Integer64': _checkInteger,
|
|
156
|
-
'cds.Int64': _checkInteger,
|
|
157
|
-
'cds.Decimal': _checkDecimal,
|
|
158
|
-
'cds.DecimalFloat': _checkNumber,
|
|
159
|
-
'cds.Double': _checkNumber,
|
|
160
|
-
'cds.Date': _checkISODate,
|
|
161
|
-
'cds.Time': _checkISOTime,
|
|
162
|
-
'cds.DateTime': _checkISODateTime,
|
|
163
|
-
'cds.Timestamp': _checkISOTimestamp,
|
|
164
|
-
'cds.String': _checkString,
|
|
165
|
-
'cds.Binary': _checkBuffer,
|
|
166
|
-
'cds.LargeString': _checkString,
|
|
167
|
-
'cds.LargeBinary': _checkBuffer
|
|
168
|
-
}
|
|
169
|
-
|
|
170
72
|
// Limitation: depth 1
|
|
171
73
|
const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
172
74
|
let found = false
|
|
@@ -174,12 +76,12 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
|
174
76
|
for (const objKey in elements) {
|
|
175
77
|
if (objKey.startsWith(`${key}_`)) {
|
|
176
78
|
const element = elements[objKey]
|
|
177
|
-
const
|
|
79
|
+
const typeChecker = typeCheckers[element._type]
|
|
178
80
|
found = true
|
|
179
81
|
|
|
180
82
|
const nestedData = value[objKey.substring(key.length + 1)]
|
|
181
83
|
// check existence of nestedData to not stumble across not-provided, yet-modelled type parts with depth > 1
|
|
182
|
-
if (nestedData && !
|
|
84
|
+
if (nestedData && !typeChecker(nestedData)) {
|
|
183
85
|
return false
|
|
184
86
|
}
|
|
185
87
|
}
|
|
@@ -188,6 +90,7 @@ const checkComplexType = ([key, value], elements, ignoreNonModelledData) => {
|
|
|
188
90
|
return found || ignoreNonModelledData
|
|
189
91
|
}
|
|
190
92
|
|
|
93
|
+
// TODO: what fails if no-op?!
|
|
191
94
|
const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonModelledData = true) => {
|
|
192
95
|
const elementsOrParameters = definition.elements || definition.params
|
|
193
96
|
if (!elementsOrParameters) return result
|
|
@@ -201,15 +104,15 @@ const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonM
|
|
|
201
104
|
return result
|
|
202
105
|
}
|
|
203
106
|
|
|
204
|
-
let
|
|
107
|
+
let typeChecker
|
|
205
108
|
if (elementOrParameter.isUUID && definition.name === 'ProvisioningService.tenant') {
|
|
206
109
|
// > old SCP accounts don't have UUID ids
|
|
207
|
-
|
|
110
|
+
typeChecker = typeCheckers['cds.String']
|
|
208
111
|
} else {
|
|
209
|
-
|
|
112
|
+
typeChecker = typeCheckers[elementOrParameter._type]
|
|
210
113
|
}
|
|
211
114
|
|
|
212
|
-
if (
|
|
115
|
+
if (typeChecker && !typeChecker(value, elementOrParameter)) {
|
|
213
116
|
// code, entity, element, value
|
|
214
117
|
const args = [typeof value === 'string' ? '"' + value + '"' : value, elementOrParameter._type]
|
|
215
118
|
result.push(assertError({ code: ASSERT_DATA_TYPE, args }, elementOrParameter, value, key))
|
|
@@ -218,131 +121,62 @@ const checkStaticElementByKey = (definition, key, value, result = [], ignoreNonM
|
|
|
218
121
|
return result
|
|
219
122
|
}
|
|
220
123
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
if (element
|
|
226
|
-
if (element._isMandatory && !element.default && _isNotFilled(value)) {
|
|
227
|
-
errors.push(assertError(ASSERT_NOT_NULL, element, value, key, pathSegmentsInfo))
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const _isNavigationColumn = (column, searched) =>
|
|
232
|
-
column.ref?.length > 1 && (column.as === searched || column.ref[column.ref.length - 1] === searched)
|
|
233
|
-
|
|
234
|
-
const _getEnumElement = element =>
|
|
235
|
-
(element['@assert.range'] && element.enum) || element['@assert.enum'] ? element.enum : undefined
|
|
236
|
-
|
|
237
|
-
const _checkEnumElement = (element, value, errors, key, pathSegmentsInfo) => {
|
|
238
|
-
const enumElements = _getEnumElement(element)
|
|
239
|
-
const enumValues = enumElements && _enumValues(enumElements)
|
|
124
|
+
/**
|
|
125
|
+
* @param {import('../../types/api').InputConstraints} constraints
|
|
126
|
+
*/
|
|
127
|
+
const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
|
|
128
|
+
if (!element) return errors
|
|
240
129
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
typeof value === 'string'
|
|
244
|
-
? ['"' + value + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
|
|
245
|
-
: [value, enumValues.join(', ')]
|
|
130
|
+
let path
|
|
131
|
+
if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
|
|
246
132
|
|
|
247
|
-
|
|
133
|
+
// not nice, but best option for keeping new cds.assert() clean
|
|
134
|
+
if (element._isMandatory) {
|
|
135
|
+
const mandatoryErrors = []
|
|
136
|
+
checkMandatory(value, element, mandatoryErrors, [], key)
|
|
137
|
+
if (mandatoryErrors.length) {
|
|
138
|
+
errors.push(...mandatoryErrors.map(() => assertError({ code: ASSERT_NOT_NULL }, element, value, key, path)))
|
|
139
|
+
}
|
|
248
140
|
}
|
|
249
|
-
}
|
|
250
141
|
|
|
251
|
-
|
|
252
|
-
const rangeElements = element['@assert.range'] && !_getEnumElement(element) ? element['@assert.range'] : undefined
|
|
253
|
-
if (rangeElements && !_checkInRange(value, rangeElements, _resolveCDSType(element))) {
|
|
254
|
-
const args = [value, ...element['@assert.range']]
|
|
255
|
-
errors.push(assertError({ code: ASSERT_RANGE, args }, element, value, key, pathSegmentsInfo))
|
|
256
|
-
}
|
|
257
|
-
}
|
|
142
|
+
if (value == null) return errors
|
|
258
143
|
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
144
|
+
// not nice, but best option for keeping new cds.assert() clean
|
|
145
|
+
const enumErrors = []
|
|
146
|
+
checkEnum(value, element, enumErrors, [], key)
|
|
147
|
+
if (enumErrors.length) {
|
|
148
|
+
errors.push(...enumErrors.map(e => assertError({ code: ASSERT_ENUM, args: e.args }, element, value, key, path)))
|
|
263
149
|
}
|
|
264
|
-
}
|
|
265
150
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
const dir = cgs[2] || '+'
|
|
272
|
-
const exp = Number(cgs[3])
|
|
273
|
-
if (dir === '+') {
|
|
274
|
-
// move decimal point to the right
|
|
275
|
-
r = r.padEnd(exp, '0')
|
|
276
|
-
l += r.substring(0, exp)
|
|
277
|
-
r = r.slice(exp)
|
|
278
|
-
val = `${l}${r ? '.' + r : ''}`
|
|
279
|
-
} else {
|
|
280
|
-
// move decimal point to the left
|
|
281
|
-
l = l.padStart(exp, '0')
|
|
282
|
-
r = l.substring(0, exp) + r
|
|
283
|
-
l = l.slice(exp)
|
|
284
|
-
val = `${l ? l : '0'}.${r}`
|
|
285
|
-
}
|
|
151
|
+
// not nice, but best option for keeping new cds.assert() clean
|
|
152
|
+
const rangeErrors = []
|
|
153
|
+
checkRange(value, element, rangeErrors, [], key)
|
|
154
|
+
if (rangeErrors.length) {
|
|
155
|
+
errors.push(...rangeErrors.map(e => assertError({ code: ASSERT_RANGE, args: e.args }, element, value, key, path)))
|
|
286
156
|
}
|
|
287
|
-
return val
|
|
288
|
-
}
|
|
289
157
|
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (precision === scale) {
|
|
298
|
-
if (!val.match(new RegExp(`^-?0\\.\\d{0,${scale}}$`, 'g'))) isValid = false
|
|
299
|
-
} else if (scale === 0) {
|
|
300
|
-
if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.0{0,1}$`, 'g'))) isValid = false
|
|
301
|
-
} else if (!val.match(new RegExp(`^-?\\d{1,${precision - scale}}\\.\\d{0,${scale}}$`, 'g'))) {
|
|
302
|
-
isValid = false
|
|
303
|
-
}
|
|
158
|
+
// not nice, but best option for keeping new cds.assert() clean
|
|
159
|
+
const formatErrors = []
|
|
160
|
+
checkFormat(value, element, formatErrors, [], key)
|
|
161
|
+
if (formatErrors.length) {
|
|
162
|
+
errors.push(...formatErrors.map(e => assertError({ code: ASSERT_FORMAT, args: e.args }, element, value, key, path)))
|
|
163
|
+
}
|
|
304
164
|
|
|
305
|
-
|
|
165
|
+
if (element.type === 'cds.Decimal') {
|
|
166
|
+
// not nice, but best option for keeping new cds.assert() clean
|
|
167
|
+
const decimalErrors = []
|
|
168
|
+
checkDecimal(value, element, decimalErrors, [], key)
|
|
169
|
+
if (decimalErrors.length) {
|
|
306
170
|
errors.push(
|
|
307
|
-
assertError(
|
|
308
|
-
{ code: ASSERT_DATA_TYPE, args: [value, `Decimal(${precision},${scale})`] },
|
|
309
|
-
element,
|
|
310
|
-
value,
|
|
311
|
-
key,
|
|
312
|
-
path
|
|
313
|
-
)
|
|
314
|
-
)
|
|
315
|
-
} else if (precision != null) {
|
|
316
|
-
if (!val.match(new RegExp(`^-?\\d{1,${precision}}$`, 'g'))) {
|
|
317
|
-
errors.push(
|
|
318
|
-
assertError({ code: ASSERT_DATA_TYPE, args: [value, `Decimal(${precision})`] }, element, value, key, path)
|
|
171
|
+
...decimalErrors.map(e => assertError({ code: ASSERT_DATA_TYPE, args: e.args }, element, value, key, path))
|
|
319
172
|
)
|
|
320
173
|
}
|
|
321
174
|
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* @param {import('../../types/api').InputConstraints} constraints
|
|
326
|
-
*/
|
|
327
|
-
const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
|
|
328
|
-
if (!element) return errors
|
|
329
|
-
|
|
330
|
-
let path
|
|
331
|
-
|
|
332
|
-
if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
|
|
333
|
-
_checkMandatoryElement(element, value, errors, key, path)
|
|
334
|
-
|
|
335
|
-
if (value == null) return errors
|
|
336
|
-
|
|
337
|
-
_checkEnumElement(element, value, errors, key, path)
|
|
338
|
-
_checkRangeElement(element, value, errors, key, path)
|
|
339
|
-
_checkFormatElement(element, value, errors, key, path)
|
|
340
|
-
|
|
341
|
-
if (element.type === 'cds.Decimal') _checkDecimalElement(element, value, errors, key, path)
|
|
342
175
|
|
|
343
176
|
return errors
|
|
344
177
|
}
|
|
345
178
|
|
|
179
|
+
// TODO: what fails if no-op?!
|
|
346
180
|
const checkStatic = (definition, data, ignoreNonModelledData = false) => {
|
|
347
181
|
if (!Array.isArray(data)) data = [data]
|
|
348
182
|
|
|
@@ -355,24 +189,6 @@ const checkStatic = (definition, data, ignoreNonModelledData = false) => {
|
|
|
355
189
|
}, [])
|
|
356
190
|
}
|
|
357
191
|
|
|
358
|
-
const checkKeys = (entity, data) => {
|
|
359
|
-
if (!Array.isArray(data)) {
|
|
360
|
-
return checkKeys(entity, [data])
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const entityKeys = Object.keys(entity.keys)
|
|
364
|
-
return data.reduce((result, row) => {
|
|
365
|
-
for (const key of entityKeys) {
|
|
366
|
-
if (row[key] === undefined && !entity.elements[key].isAssociation)
|
|
367
|
-
result.push(assertError(ASSERT_NOT_NULL, entity.elements[key]))
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return result
|
|
371
|
-
}, [])
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
const assertNotNullError = element => assertError(ASSERT_NOT_NULL, element)
|
|
375
|
-
|
|
376
192
|
/**
|
|
377
193
|
* Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
|
|
378
194
|
* the the reference's target doesn't exist.
|
|
@@ -428,14 +244,8 @@ const assertTargets = async (assertMap, errors) => {
|
|
|
428
244
|
}
|
|
429
245
|
|
|
430
246
|
module.exports = {
|
|
431
|
-
CDS_TYPE_CHECKS,
|
|
432
|
-
checkComplexType,
|
|
433
247
|
checkStatic,
|
|
434
|
-
checkInputConstraints,
|
|
435
|
-
checkKeys,
|
|
436
|
-
assertError,
|
|
437
248
|
checkStaticElementByKey,
|
|
438
|
-
|
|
439
|
-
assertTargets
|
|
440
|
-
getNormalizedDecimal
|
|
249
|
+
checkInputConstraints,
|
|
250
|
+
assertTargets
|
|
441
251
|
}
|
package/libx/_runtime/cds.js
CHANGED
|
@@ -10,6 +10,11 @@ cds.extend(any).with(require('./common/aspects/any'))
|
|
|
10
10
|
cds.extend(Association).with(require('./common/aspects/Association'))
|
|
11
11
|
cds.extend(entity).with(require('./common/aspects/entity'))
|
|
12
12
|
|
|
13
|
+
/*
|
|
14
|
+
* cds.assert(data, definition, options?)
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(cds, 'assert', { get: () => require('../common/assert') })
|
|
17
|
+
|
|
13
18
|
/**
|
|
14
19
|
* Logs a deprecation warning once per process
|
|
15
20
|
*
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
const { foreignKey4 } = require('../../common/utils/foreignKeyPropagations')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
get _isMandatory() {
|
|
11
|
-
return this.own('__isMandatory', () => !this.isAssociation && isMandatory(this))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
get _isReadOnly() {
|
|
15
|
-
return this.own('__isReadOnly', () => !this.key && isReadOnly(this))
|
|
16
|
-
}
|
|
3
|
+
const _getCommonFieldControl = e => {
|
|
4
|
+
const cfr = e['@Common.FieldControl']
|
|
5
|
+
return cfr && cfr['#']
|
|
6
|
+
}
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
const _isMandatory = e => {
|
|
9
|
+
return (
|
|
10
|
+
e['@assert.mandatory'] !== false &&
|
|
11
|
+
(e['@mandatory'] ||
|
|
12
|
+
e['@Common.FieldControl.Mandatory'] ||
|
|
13
|
+
e['@FieldControl.Mandatory'] ||
|
|
14
|
+
_getCommonFieldControl(e) === 'Mandatory')
|
|
15
|
+
)
|
|
16
|
+
}
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const _isReadOnly = e => {
|
|
19
|
+
return (
|
|
20
|
+
e['@readonly'] ||
|
|
21
|
+
e['@cds.on.update'] ||
|
|
22
|
+
e['@cds.on.insert'] ||
|
|
23
|
+
e['@Core.Computed'] ||
|
|
24
|
+
e['@Common.FieldControl.ReadOnly'] ||
|
|
25
|
+
e['@FieldControl.ReadOnly'] ||
|
|
26
|
+
_getCommonFieldControl(e) === 'ReadOnly'
|
|
27
|
+
)
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const Relation = require('./relation')
|
|
31
|
+
|
|
29
32
|
const _exposeRelation = relation => Object.defineProperty({}, '_', { get: () => relation })
|
|
30
33
|
|
|
31
34
|
const _relationHandler = relation => ({
|
|
@@ -52,34 +55,39 @@ const _relationHandler = relation => ({
|
|
|
52
55
|
}
|
|
53
56
|
})
|
|
54
57
|
|
|
55
|
-
const
|
|
58
|
+
const _getRelations = e => {
|
|
56
59
|
const newRelation = Relation.to(e)
|
|
57
60
|
return new Proxy(_exposeRelation(newRelation), _relationHandler(newRelation))
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
// NOTE: Please only add things which are relevant to _any_ type,
|
|
64
|
+
// use specialized types otherwise (entity, Association, ...).
|
|
65
|
+
module.exports = class {
|
|
66
|
+
get _isStructured() {
|
|
67
|
+
return this.own('__isStructured', () => !!this.elements && this.kind !== 'entity')
|
|
68
|
+
}
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
(e['@mandatory'] ||
|
|
69
|
-
e['@Common.FieldControl.Mandatory'] ||
|
|
70
|
-
e['@FieldControl.Mandatory'] ||
|
|
71
|
-
CommonFieldControl(e) === 'Mandatory')
|
|
72
|
-
)
|
|
73
|
-
}
|
|
70
|
+
get _isMandatory() {
|
|
71
|
+
return this.own('__isMandatory', () => !this.isAssociation && _isMandatory(this))
|
|
72
|
+
}
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
74
|
+
get _isReadOnly() {
|
|
75
|
+
return this.own('__isReadOnly', () => !this.key && _isReadOnly(this))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get _mandatories() {
|
|
79
|
+
return this.own(
|
|
80
|
+
'__mandatories',
|
|
81
|
+
() => this.elements && Object.entries(this.elements).filter(([_, v]) => v._isMandatory)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// REVISIT: Where to put?
|
|
86
|
+
get _relations() {
|
|
87
|
+
return this.own('__relations', () => _getRelations(this))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get _foreignKey4() {
|
|
91
|
+
return this.own('__foreignKey4', () => foreignKey4(this))
|
|
92
|
+
}
|
|
85
93
|
}
|
|
@@ -179,19 +179,23 @@ const _pick = element => {
|
|
|
179
179
|
categories.push({ category: 'propagateForeignKeys' })
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
182
|
+
if (!cds.env.features.cds_assert) {
|
|
183
|
+
if (
|
|
184
|
+
element['@assert.range'] ||
|
|
185
|
+
element['@assert.enum'] ||
|
|
186
|
+
element['@assert.format'] ||
|
|
187
|
+
element.type === 'cds.Decimal'
|
|
188
|
+
) {
|
|
189
|
+
categories.push('assert')
|
|
190
|
+
}
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
if (element._isMandatory) {
|
|
193
|
+
categories.push('mandatory')
|
|
194
|
+
}
|
|
193
195
|
}
|
|
194
196
|
|
|
197
|
+
// TODO: can we move more checks to cds.assert()?
|
|
198
|
+
|
|
195
199
|
if (element._isReadOnly) {
|
|
196
200
|
// > _isReadOnly includes @cds.on.insert and @cds.on.update
|
|
197
201
|
categories.push('readonly')
|
|
@@ -21,7 +21,7 @@ const getPageSize = def => {
|
|
|
21
21
|
DEFAULT
|
|
22
22
|
if (!max) max = Number.MAX_SAFE_INTEGER
|
|
23
23
|
if (!_default || _default > max) _default = max
|
|
24
|
-
return (
|
|
24
|
+
return def.set(_cached, { default: _default, max })
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const commonGenericPaging = function (req) {
|
|
@@ -12,7 +12,7 @@ const getError = require('../../common/error')
|
|
|
12
12
|
const { rewriteAsterisks } = require('./rewriteAsterisks')
|
|
13
13
|
const { getEntityFromPath } = require('../../common/utils/path')
|
|
14
14
|
const { removeIsActiveEntityRecursively } = require('../../fiori/utils/where')
|
|
15
|
-
const { addRefToWhereIfNecessary } = require('../../../odata/afterburner')
|
|
15
|
+
const { addRefToWhereIfNecessary } = require('../../../odata/parse/afterburner')
|
|
16
16
|
const { addAliasToExpression, PARENT_ALIAS, FOREIGN_ALIAS } = require('../../db/utils/generateAliases')
|
|
17
17
|
const { getColumns } = require('../../cds-services/services/utils/columns')
|
|
18
18
|
|
|
@@ -57,7 +57,7 @@ function _buildWhereForNavigations(ref, newWhere, model, target) {
|
|
|
57
57
|
|
|
58
58
|
if (!navigationElement || !navigationElement.on) return
|
|
59
59
|
|
|
60
|
-
const onCond = getOnCond(navigationElement, [], { select: 'source', join: 'target' })
|
|
60
|
+
const onCond = getOnCond(navigationElement, [navigationElement.name], { select: 'source', join: 'target' })
|
|
61
61
|
const nextKeys = _getOnCondElements(onCond[0].xpr)
|
|
62
62
|
|
|
63
63
|
// only add where once in _modifyWhereWithNavigations
|
|
@@ -13,17 +13,20 @@ const _flattenProps = (element, structProperties, asRef, withKey, prefix) => {
|
|
|
13
13
|
if (structProperties.length && element.is2one && !element.on) {
|
|
14
14
|
const resolved = [...prefix, ...structProperties]
|
|
15
15
|
if (withKey) {
|
|
16
|
+
// TODO: should be "element._alias || element.name", but how to get alias from edm?
|
|
17
|
+
// cf. removed alias2ref (https://github.tools.sap/cap/cds/pull/3583)
|
|
16
18
|
return [{ key: element.name, resolved }]
|
|
17
19
|
}
|
|
18
20
|
const flattenedName = resolved.join('_')
|
|
19
21
|
return asRef ? [{ ref: [flattenedName] }] : [flattenedName]
|
|
20
22
|
}
|
|
21
|
-
|
|
22
23
|
return []
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const resolved = [...prefix, element.name]
|
|
26
27
|
if (withKey) {
|
|
28
|
+
// TODO: should be "element._alias || element.name", but how to get alias from edm?
|
|
29
|
+
// cf. removed alias2ref (https://github.tools.sap/cap/cds/pull/3583)
|
|
27
30
|
return [{ key: element.name, resolved }]
|
|
28
31
|
}
|
|
29
32
|
const flattenedName = resolved.join('_')
|
|
@@ -68,22 +68,15 @@ const rewriteExpandAsterisk = (columns, target) => {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const _isLargeBinary = (col, target) => target.elements[col.ref[col.ref.length - 1]]?.type === 'cds.LargeBinary'
|
|
72
|
-
|
|
73
71
|
const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
74
72
|
const asteriskColumnIndex = columns.findIndex(col => isAsteriskColumn(col))
|
|
75
73
|
if (asteriskColumnIndex > -1) {
|
|
76
74
|
columns.splice(
|
|
77
75
|
asteriskColumnIndex,
|
|
78
76
|
1,
|
|
79
|
-
...getColumns(target, { _4db })
|
|
77
|
+
...getColumns(target, { _4db, omitStream: !cds.env.features.stream_compat })
|
|
80
78
|
.map(c => ({ ref: [c.name] }))
|
|
81
|
-
.filter(
|
|
82
|
-
c =>
|
|
83
|
-
!columns.find(isDuplicate(c)) &&
|
|
84
|
-
(!_4db || cds.env.features.stream_compat || !_isLargeBinary(c, target)) &&
|
|
85
|
-
(isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID')
|
|
86
|
-
)
|
|
79
|
+
.filter(c => !columns.find(isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
|
|
87
80
|
)
|
|
88
81
|
}
|
|
89
82
|
}
|
|
@@ -150,9 +143,9 @@ const rewriteAsterisks = (query, model, options) => {
|
|
|
150
143
|
const target = _targetOfQueryIfNotDraft(query, model)
|
|
151
144
|
if (!target) return
|
|
152
145
|
|
|
153
|
-
query.SELECT.columns = getColumns(target, { _4db })
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
query.SELECT.columns = getColumns(target, { _4db, omitStream: !cds.env.features.stream_compat }).map(col => ({
|
|
147
|
+
ref: [col.name]
|
|
148
|
+
}))
|
|
156
149
|
if (_4db && target._isDraftEnabled && !cds.env.fiori.lean_draft)
|
|
157
150
|
query.SELECT.columns.push(..._cqlDraftColumns(target))
|
|
158
151
|
}
|
|
@@ -117,25 +117,11 @@ const enhanceStreamResult = async (req, query, result, model) => {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
const pick = element => {
|
|
120
|
-
return element['@Core.IsURL']
|
|
120
|
+
return element['@Core.IsURL'] || element['@Core.MediaType']
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
const processFn = ({ row, key }) => {
|
|
124
|
-
row[`${key}@odata.mediaReadLink`] = row[key]
|
|
125
124
|
delete row[key]
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
if (!result) return
|
|
130
|
-
if (!Array.isArray(result)) result = [result]
|
|
131
|
-
if (result.length === 0) return
|
|
132
|
-
|
|
133
|
-
const template = getTemplate('redirect-properties', service, req.target, { pick })
|
|
134
|
-
if (template && template.elements.size) {
|
|
135
|
-
for (const row of result) {
|
|
136
|
-
templateProcessor({ processFn, row, template })
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
module.exports = { enhanceStreamResult, transformRedirectProperties }
|
|
127
|
+
module.exports = { enhanceStreamResult }
|