@sap/cds 9.0.4 → 9.2.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 (77) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/bin/deploy.js +29 -0
  3. package/bin/serve.js +1 -5
  4. package/lib/compile/etc/csv.js +11 -6
  5. package/lib/compile/for/lean_drafts.js +29 -7
  6. package/lib/compile/load.js +8 -5
  7. package/lib/compile/to/hdbtabledata.js +1 -1
  8. package/lib/dbs/cds-deploy.js +5 -34
  9. package/lib/env/cds-env.js +2 -1
  10. package/lib/env/cds-requires.js +4 -1
  11. package/lib/env/defaults.js +0 -11
  12. package/lib/env/schemas/cds-rc.js +218 -6
  13. package/lib/index.js +38 -38
  14. package/lib/log/cds-error.js +12 -11
  15. package/lib/log/format/json.js +1 -1
  16. package/lib/ql/SELECT.js +31 -0
  17. package/lib/ql/resolve.js +1 -1
  18. package/lib/req/context.js +1 -1
  19. package/lib/req/request.js +1 -1
  20. package/lib/req/validate.js +17 -19
  21. package/lib/srv/cds.Service.js +18 -28
  22. package/lib/srv/middlewares/auth/ias-auth.js +29 -2
  23. package/lib/srv/middlewares/auth/jwt-auth.js +11 -1
  24. package/lib/srv/middlewares/auth/xssec.js +1 -1
  25. package/lib/srv/srv-models.js +1 -1
  26. package/lib/srv/srv-tx.js +2 -2
  27. package/lib/utils/cds-utils.js +35 -2
  28. package/lib/utils/csv-reader.js +1 -1
  29. package/lib/utils/inflect.js +2 -2
  30. package/lib/utils/tar.js +60 -23
  31. package/lib/utils/version.js +18 -0
  32. package/libx/_runtime/cds.js +1 -1
  33. package/libx/_runtime/common/aspects/any.js +1 -23
  34. package/libx/_runtime/common/generic/crud.js +1 -3
  35. package/libx/_runtime/common/generic/input.js +113 -52
  36. package/libx/_runtime/common/generic/sorting.js +1 -1
  37. package/libx/_runtime/common/generic/temporal.js +0 -6
  38. package/libx/_runtime/common/utils/draft.js +1 -1
  39. package/libx/_runtime/common/utils/entityFromCqn.js +1 -1
  40. package/libx/_runtime/common/utils/propagateForeignKeys.js +1 -1
  41. package/libx/_runtime/common/utils/resolveView.js +2 -2
  42. package/libx/_runtime/common/utils/structured.js +2 -2
  43. package/libx/_runtime/common/utils/templateProcessor.js +0 -5
  44. package/libx/_runtime/common/utils/vcap.js +1 -1
  45. package/libx/_runtime/fiori/lean-draft.js +529 -143
  46. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -2
  47. package/libx/_runtime/messaging/service.js +1 -1
  48. package/libx/_runtime/remote/utils/client.js +2 -1
  49. package/libx/common/assert/utils.js +2 -12
  50. package/libx/common/utils/streaming.js +4 -9
  51. package/libx/http/location.js +1 -0
  52. package/libx/odata/ODataAdapter.js +47 -43
  53. package/libx/odata/index.js +1 -1
  54. package/libx/odata/middleware/batch.js +6 -2
  55. package/libx/odata/middleware/create.js +1 -1
  56. package/libx/odata/middleware/error.js +27 -17
  57. package/libx/odata/middleware/operation.js +15 -21
  58. package/libx/odata/middleware/stream.js +1 -1
  59. package/libx/odata/parse/afterburner.js +22 -8
  60. package/libx/odata/parse/cqn2odata.js +16 -10
  61. package/libx/odata/parse/grammar.peggy +185 -134
  62. package/libx/odata/parse/parser.js +1 -1
  63. package/libx/odata/utils/index.js +1 -36
  64. package/libx/odata/utils/metadata.js +34 -1
  65. package/libx/odata/utils/odataBind.js +2 -1
  66. package/libx/odata/utils/result.js +22 -20
  67. package/libx/queue/index.js +7 -4
  68. package/libx/rest/RestAdapter.js +1 -2
  69. package/libx/rest/middleware/create.js +5 -2
  70. package/package.json +2 -2
  71. package/server.js +1 -1
  72. package/bin/deploy/to-hana.js +0 -1
  73. package/lib/utils/check-version.js +0 -9
  74. package/lib/utils/unit.js +0 -19
  75. package/libx/_runtime/cds-services/util/assert.js +0 -181
  76. package/libx/_runtime/types/api.js +0 -129
  77. package/libx/common/assert/validation.js +0 -109
@@ -49,6 +49,8 @@ const _safeJSONParse = string => {
49
49
  }
50
50
  }
51
51
 
52
+ const _targetName = (name, opts) => (opts.targetPrefix ? opts.targetPrefix + name : name)
53
+
52
54
  // Note: This function can also run for each tenant on startup
53
55
  //
54
56
  // tx1: Fetch messages which are not in process and are not locked (SELECT FOR UPDATE)
@@ -85,7 +87,7 @@ const processTasks = (service, tenant, _opts = {}) => {
85
87
  const spawn = _begin(async () => {
86
88
  let selectedTasks
87
89
  const currTime = Date.now()
88
- const _timeout = cds.utils._unit.time2ms(opts.timeout)
90
+ const _timeout = cds.utils.ms4(opts.timeout)
89
91
 
90
92
  let currMinWaitingTime
91
93
 
@@ -95,7 +97,7 @@ const processTasks = (service, tenant, _opts = {}) => {
95
97
  }
96
98
 
97
99
  const tasksQuery = SELECT.from(tasksEntity)
98
- .where({ target: name })
100
+ .where({ target: _targetName(name, opts) })
99
101
  .orderBy(opts.parallel ? ['status', 'timestamp', 'ID'] : ['timestamp', 'status', 'ID'])
100
102
  .limit(opts.chunkSize)
101
103
  .forUpdate()
@@ -232,7 +234,7 @@ const processTasks = (service, tenant, _opts = {}) => {
232
234
  toBeUpdated.push(task)
233
235
  } else toBeDeleted.push(task)
234
236
  } else {
235
- LOG.error(`${service.name}: Emit failed:`, e)
237
+ cds.repl || LOG.error(`${service.name}: Emit failed:`, e)
236
238
  task.updateData = { attempts: task.attempts + 1 }
237
239
  toBeUpdated.push(task)
238
240
  return false
@@ -294,6 +296,7 @@ const processTasks = (service, tenant, _opts = {}) => {
294
296
  const _newMsgFrom = msg => {
295
297
  const _fromSend = msg instanceof cds.Request
296
298
  const newMsg = { ...msg }
299
+ if (msg.entity) newMsg.entity = msg.entity // needed for proper `h.for(msg)` handling
297
300
  newMsg._fromSend = _fromSend
298
301
  if (!newMsg.queue) return newMsg
299
302
  if (!newMsg.queue.after && !newMsg.queue.every) return newMsg
@@ -428,7 +431,7 @@ const _createTask = (name, msg, context, taskOpts) => {
428
431
  }
429
432
  const taskMsg = {
430
433
  ID: cds.utils.uuid(),
431
- target: name,
434
+ target: _targetName(name, taskOpts),
432
435
  timestamp: _get100NanosecondTimestampISOString(msg.queue?.after), // needs to be different for each emit
433
436
  msg: JSON.stringify(_msg)
434
437
  }
@@ -72,7 +72,7 @@ module.exports = class RestAdapter extends HttpAdapter {
72
72
  // if authentication or something else within the processing of a cds.Request terminates the request, no need to continue
73
73
  if (res.headersSent) return next()
74
74
 
75
- const { result, status, location } = outcome
75
+ const { result, status } = outcome
76
76
 
77
77
  // post process
78
78
  if (result) {
@@ -83,7 +83,6 @@ module.exports = class RestAdapter extends HttpAdapter {
83
83
  }
84
84
 
85
85
  if (status && res.statusCode === 200) res.status(status) //> only set status if not yet modified
86
- if (location && !res.get('location')) res.set('location', location)
87
86
 
88
87
  if (req.method === 'HEAD') res.type('json').set('content-length', JSON.stringify(result).length).end()
89
88
  else res.send(typeof result === 'number' ? result.toString() : result)
@@ -25,10 +25,13 @@ exports.create = async function (req, res) {
25
25
  // > single insert
26
26
  const cdsReq = this.request4({ query, params, req, res })
27
27
  result = await this.service.dispatch(cdsReq)
28
- location = location4(cdsReq.target, this.service, result, true)
28
+ if (!res.hasHeader('location')) {
29
+ location = location4(cdsReq.target, this.service, result, true)
30
+ if (location) res.set('location', location)
31
+ }
29
32
  }
30
33
 
31
- return { result, status: 201, location }
34
+ return { result, status: 201 }
32
35
  }
33
36
 
34
37
  const cds = require('../../_runtime/cds')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds",
3
- "version": "9.0.4",
3
+ "version": "9.2.0",
4
4
  "description": "SAP Cloud Application Programming Model - CDS for Node.js",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  "license": "SEE LICENSE IN LICENSE",
12
12
  "main": "lib/index.js",
13
13
  "bin": {
14
- "cds-deploy": "lib/dbs/cds-deploy.js",
14
+ "cds-deploy": "bin/deploy.js",
15
15
  "cds-serve": "bin/serve.js"
16
16
  },
17
17
  "files": [
package/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const express = require('express')
2
- const cds = require('./lib')
2
+ const cds = require('./lib/index.js')
3
3
 
4
4
  /**
5
5
  * Standard express.js bootstrapping, constructing an express `application`
@@ -1 +0,0 @@
1
- throw new Error('This application uses @sap/cds version >= 7, which is not compatible with the installed @sap/cds-dk version 6. Either update @sap/cds-dk to version 7 or downgrade @sap/cds to version 6 instead.')
@@ -1,9 +0,0 @@
1
- let version = /(\d+)(?:\.(\d+).*)?/ .exec (require('../../package.json').engines.node)
2
- let given = /(\d+)(?:\.(\d+).*)?/ .exec (process.version)
3
- if (+given[1] < +version[1] || given[1] == version[1] && +given[2] < +version[2]) {
4
- process.stderr.write (`
5
- Node.js version ${version[0]} or higher is required for @sap/cds.
6
- Current version ${given[0]} does not satisfy this.
7
- \n`)
8
- process.exit(1)
9
- }
package/lib/utils/unit.js DELETED
@@ -1,19 +0,0 @@
1
- module.exports.time2ms = timeout => {
2
- const match = timeout.match(/^([0-9]+)(w|d|h|hrs|min)$/)
3
- if (!match) return
4
-
5
- const [, val, t] = match
6
- switch (t) {
7
- case 'w':
8
- return val * 1000 * 3600 * 24 * 7
9
- case 'd':
10
- return val * 1000 * 3600 * 24
11
- case 'h':
12
- case 'hrs':
13
- return val * 1000 * 3600
14
- case 'min':
15
- return val * 1000 * 60
16
- default:
17
- return val
18
- }
19
- }
@@ -1,181 +0,0 @@
1
- const cds = require('../../cds')
2
- const LOG = cds.log('app')
3
- const templatePathSerializer = require('../../common/utils/templateProcessorPathSerializer')
4
-
5
- const typeCheckers = require('../../../common/assert/type-strict')
6
- const { 'cds.Decimal': checkDecimal } = typeCheckers
7
- const { checkMandatory, checkEnum, checkRange, checkFormat } = require('../../../common/assert/validation')
8
-
9
- const ASSERT_RANGE = 'ASSERT_RANGE'
10
- const ASSERT_FORMAT = 'ASSERT_FORMAT'
11
- const ASSERT_DATA_TYPE = 'ASSERT_DATA_TYPE'
12
- const ASSERT_ENUM = 'ASSERT_ENUM'
13
- const ASSERT_NOT_NULL = 'ASSERT_NOT_NULL'
14
-
15
- const _enumValues = element => {
16
- return Object.keys(element).map(enumKey => {
17
- const enum_ = element[enumKey]
18
- const enumValue = enum_ && enum_.val
19
-
20
- if (enumValue !== undefined) {
21
- if (enumValue['=']) return enumValue['=']
22
- if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
23
- return enumValue
24
- }
25
-
26
- return enumKey
27
- })
28
- }
29
-
30
- // REVISIT: this needs a cleanup!
31
- const assertError = (code, element, value, key, path) => {
32
- let args
33
-
34
- if (typeof code === 'object') {
35
- args = code.args
36
- code = code.code
37
- }
38
-
39
- const { name, type, precision, scale } = element
40
- const error = new Error()
41
- const errorEntry = {
42
- code,
43
- message: code,
44
- target: path ?? element.name ?? key,
45
- args: args ?? [name ?? key]
46
- }
47
-
48
- const assertError = Object.assign(error, errorEntry)
49
- Object.assign(assertError, {
50
- entity: element.parent && element.parent.name,
51
- element: name, // > REVISIT: when is error.element needed?
52
- type: element.items ? element.items._type : type,
53
- status: 400,
54
- value
55
- })
56
-
57
- if (element.enum) assertError.enum = _enumValues(element)
58
- if (precision) assertError.precision = precision
59
- if (scale) assertError.scale = scale
60
-
61
- if (element.target) {
62
- // REVISIT: when does this case apply?
63
- assertError.target = element.target
64
- }
65
-
66
- return assertError
67
- }
68
-
69
- /**
70
- * @param {import('../../types/api').InputConstraints} constraints
71
- */
72
- const checkInputConstraints = ({ element, value, errors, key, pathSegmentsInfo }) => {
73
- if (!element) return errors
74
-
75
- let path
76
- if (pathSegmentsInfo?.length) path = templatePathSerializer(element.name || key, pathSegmentsInfo)
77
-
78
- // not nice, but best option for keeping new cds.assert() clean
79
- if (element._isMandatory) {
80
- const mandatoryErrors = []
81
- checkMandatory(value, element, mandatoryErrors, [], key)
82
- if (mandatoryErrors.length) {
83
- errors.push(...mandatoryErrors.map(() => assertError({ code: ASSERT_NOT_NULL }, element, value, key, path)))
84
- }
85
- }
86
-
87
- if (value == null) return errors
88
-
89
- // not nice, but best option for keeping new cds.assert() clean
90
- const enumErrors = []
91
- checkEnum(value, element, enumErrors, [], key)
92
- if (enumErrors.length) {
93
- errors.push(...enumErrors.map(e => assertError({ code: ASSERT_ENUM, args: e.args }, element, value, key, path)))
94
- }
95
-
96
- // not nice, but best option for keeping new cds.assert() clean
97
- const rangeErrors = []
98
- checkRange(value, element, rangeErrors, [], key)
99
- if (rangeErrors.length) {
100
- errors.push(...rangeErrors.map(e => assertError({ code: ASSERT_RANGE, args: e.args }, element, value, key, path)))
101
- }
102
-
103
- // not nice, but best option for keeping new cds.assert() clean
104
- const formatErrors = []
105
- checkFormat(value, element, formatErrors, [], key)
106
- if (formatErrors.length) {
107
- errors.push(...formatErrors.map(e => assertError({ code: ASSERT_FORMAT, args: e.args }, element, value, key, path)))
108
- }
109
-
110
- if (element.type === 'cds.Decimal') {
111
- // not nice, but best option for keeping new cds.assert() clean
112
- const decimalErrors = []
113
- checkDecimal(value, element, decimalErrors, [], key)
114
- if (decimalErrors.length) {
115
- errors.push(
116
- ...decimalErrors.map(e => assertError({ code: ASSERT_DATA_TYPE, args: e.args }, element, value, key, path))
117
- )
118
- }
119
- }
120
-
121
- return errors
122
- }
123
-
124
- /**
125
- * Check whether the target entity referenced by the association (the reference's target) exists and assert an error if
126
- * the the reference's target doesn't exist.
127
- *
128
- * In other words, use this annotation to check whether a non-null foreign key input in a table has a corresponding
129
- * primary key (also known as a parent key) in the associated/referenced target table (also known as a parent table).
130
- *
131
- * @param {import('../../types/api').assertTargetMap} assertMap
132
- * @param {array} errors An array to appends the possible errors.
133
- * @see {@link https://cap.cloud.sap/docs/guides/providing-services#assert-target @assert.target} for
134
- * further information.
135
- */
136
- const assertTargets = async (assertMap, errors) => {
137
- const { targets: targetsMap, allTargets } = assertMap
138
- if (targetsMap.size === 0) return
139
-
140
- const targets = Array.from(targetsMap.values())
141
- const transactions = targets.map(({ keys, entity }) => {
142
- const where = Object.assign({}, ...keys)
143
- return cds.db.exists(entity, where).forShareLock()
144
- })
145
- const targetsExistsResults = await Promise.allSettled(transactions)
146
-
147
- targetsExistsResults.forEach((txPromise, index) => {
148
- const isPromiseRejected = txPromise.status === 'rejected'
149
- const shouldAssertError = (txPromise.status === 'fulfilled' && txPromise.value == null) || isPromiseRejected
150
- if (!shouldAssertError) return
151
-
152
- const target = targets[index]
153
- const { element } = target.assocInfo
154
-
155
- if (isPromiseRejected) {
156
- LOG._debug &&
157
- LOG.debug(
158
- `The transaction to check the @assert.target constraint for foreign key "${element.name}" failed`,
159
- txPromise.reason
160
- )
161
-
162
- throw new Error(txPromise.reason.message)
163
- }
164
-
165
- allTargets
166
- .filter(t => t.key === target.key)
167
- .forEach(target => {
168
- const { row, pathSegmentsInfo } = target.assocInfo
169
- const key = target.foreignKey.name
170
- let path
171
- if (pathSegmentsInfo?.length) path = templatePathSerializer(key, pathSegmentsInfo)
172
- const error = assertError('ASSERT_TARGET', target.foreignKey, row[key], key, path)
173
- errors.push(error)
174
- })
175
- })
176
- }
177
-
178
- module.exports = {
179
- checkInputConstraints,
180
- assertTargets
181
- }
@@ -1,129 +0,0 @@
1
- // Columns
2
-
3
- /**
4
- * @typedef {object} ColumnRef
5
- * @property {string[]} ref
6
- * @property {function} func
7
- */
8
-
9
- /**
10
- * @typedef {Array<ColumnRef>} ColumnRefs
11
- */
12
-
13
- // Input constraints
14
-
15
- /**
16
- * @typedef {object} InputConstraints
17
- * @property {object} element
18
- * @property {*} value
19
- * @property {Array} errors
20
- * @property {string} [key]
21
- * @property {pathSegmentInfo[]} [pathSegmentsInfo]
22
- * @property {string} event
23
- */
24
-
25
- // ON condition
26
-
27
- /**
28
- * @typedef {object} ONConditionAliases
29
- * @property {string} select
30
- * @property {string} join
31
- */
32
-
33
- /**
34
- * @typedef {object} ONConditionOptions
35
- * @property {string | Array} [associationNames]
36
- * @property {object} [csn]
37
- * @property {ONConditionAliases} [aliases]
38
- * @property {boolean} [resolveView=true]
39
- */
40
-
41
- // Template processor
42
-
43
- /**
44
- * @typedef {object} TemplateProcessorInfo
45
- * @property {entity} target
46
- * @property {Map} elements
47
- */
48
-
49
- /**
50
- * @typedef {object} TemplateProcessorPathOptions
51
- * @property {object} [draftKeys]
52
- * @property {function} [rowUUIDGenerator]
53
- * @property {string[]} [segments=[]] - Path segments to relate the error message.
54
- * @property {boolean} [includeKeyValues=false] Indicates whether the key values are included in the path segments
55
- * The path segments are used to build the error target (a relative resource path)
56
- */
57
-
58
- /**
59
- * @typedef {object} TemplateProcessor
60
- * @property {Function} processFn
61
- * @property {object} data
62
- * @property {TemplateProcessorInfo} template
63
- * @property {boolean} [isRoot=true]
64
- * @property {TemplateProcessorPathOptions} [pathOptions=null]
65
- */
66
-
67
- /**
68
- * @typedef {object} pathSegmentInfo
69
- * @property {string} key
70
- * @property {string[]} keyNames
71
- * @property {object} row
72
- * @property {object} elements
73
- * @property {string[]} draftKeys
74
- */
75
-
76
- /**
77
- * @typedef {object} templateElementInfo
78
- * @property {object} row
79
- * @property {string} key
80
- * @property {object} element
81
- * @property {boolean} plain
82
- * @property {entity} target
83
- * @property {boolean} isRoot
84
- * @property {string[] | Array<pathSegmentInfo>} [pathSegmentsInfo]
85
- */
86
-
87
- // Search
88
-
89
- /**
90
- * @typedef {object} searchContainsArg
91
- * @property {ColumnRefs} [list] The columns to
92
- * be searched
93
- * @property {string} [val] The search string
94
- */
95
-
96
- /**
97
- * @typedef {Array<searchContainsArg>} searchContainsArgs
98
- */
99
-
100
- /**
101
- * @typedef {object} searchContainsExp
102
- * @property {string} func='contains' The function name
103
- * @property {searchContainsArgs} args
104
- */
105
-
106
- /**
107
- * @typedef {object} search2cqnOptions
108
- * @property {ColumnRefs} [columns] The columns to be searched
109
- * @property {string} locale The user locale
110
- */
111
-
112
- // Assert targets map
113
-
114
- /**
115
- * @typedef {object} assertTargetMap
116
- * @property {Map<string, targetMaps>} targets
117
- * @property {targetMaps[]} allTargets
118
- */
119
-
120
- /**
121
- * @typedef {object} targetMaps
122
- * @property {string} key
123
- * @property {entity} entity
124
- * @property {object} keys
125
- * @property {object} foreignKey
126
- * @property {templateElementInfo} assocInfo
127
- */
128
-
129
- module.exports = {}
@@ -1,109 +0,0 @@
1
- const { cds } = global
2
-
3
- const {
4
- 'cds.Date': checkISODate,
5
- 'cds.Time': checkISOTime,
6
- 'cds.DateTime': checkISODateTime,
7
- 'cds.Timestamp': checkISOTimestamp,
8
- 'cds.String': checkString
9
- } = require('./type-strict')
10
- const { getTarget, resolveCDSType } = require('./utils')
11
-
12
- const _isNavigationColumn = (col, as) => col.ref?.length > 1 && (col.as === as || col.ref[col.ref.length - 1] === as)
13
-
14
- // REVISIT: mandatory is actually not the same as not null or empty string
15
- const _isNotFilled = val => val === null || val === undefined || (typeof val === 'string' && val.trim() === '')
16
-
17
- const _getEnumElement = ele => ((ele['@assert.range'] && ele.enum) ? ele.enum : undefined)
18
-
19
- const _enumValues = ele => {
20
- return Object.keys(ele).map(enumKey => {
21
- const enum_ = ele[enumKey]
22
- const enumValue = enum_ && enum_.val
23
- if (enumValue !== undefined) {
24
- if (enumValue['=']) return enumValue['=']
25
- if (enum_ && enum_.literal && enum_.literal === 'number') return Number(enumValue)
26
- return enumValue
27
- }
28
- return enumKey
29
- })
30
- }
31
-
32
- const _checkDateValue = (val, r1, r2) => {
33
- const dateVal = new Date(val)
34
- return (dateVal - new Date(r1)) * (dateVal - new Date(r2)) <= 0
35
- }
36
-
37
- const _toDate = val => `2000-01-01T${val}Z`
38
-
39
- const _checkInRange = (val, range, type) => {
40
- switch (type) {
41
- case 'cds.Date':
42
- return checkISODate(val) && _checkDateValue(val, range[0], range[1])
43
- case 'cds.DateTime':
44
- return checkISODateTime(val) && _checkDateValue(val, range[0], range[1])
45
- case 'cds.Timestamp':
46
- return checkISOTimestamp(val) && _checkDateValue(val, range[0], range[1])
47
- case 'cds.Time':
48
- return checkISOTime(val) && _checkDateValue(_toDate(val), _toDate(range[0]), _toDate(range[1]))
49
- default:
50
- return (val - range[0]) * (val - range[1]) <= 0
51
- }
52
- }
53
-
54
- // process.env.CDS_ASSERT_FORMAT_FLAGS is not official!
55
- const _checkRegExpFormat = (val, format) =>
56
- checkString(val) && val.match(new RegExp(format, process.env.CDS_ASSERT_FORMAT_FLAGS || 'u'))
57
-
58
- const checkMandatory = (v, ele, errs, path, k) => {
59
- // REVISIT: correct to not complain?
60
- // do not complain about missing foreign keys in children
61
- if (path.length && ele['@odata.foreignKey4']) return
62
-
63
- // TODO: which case is this?
64
- // do not complain about ???
65
- if (ele.parent?.query?.SELECT?.columns?.find(col => _isNavigationColumn(col, ele.name))) return
66
-
67
- if (_isNotFilled(v)) {
68
- const target = getTarget(path, k)
69
- errs.push(new cds.error('ASSERT_NOT_NULL', { target, statusCode: 400, code: '400' }))
70
- }
71
- }
72
-
73
- const checkEnum = (v, ele, errs, path, k) => {
74
- const enumElements = _getEnumElement(ele)
75
- const enumValues = enumElements && _enumValues(enumElements)
76
- if (enumElements && !enumValues.some(ev => ev == v)) { //> use == for automatic type coercion
77
- const args =
78
- typeof v === 'string'
79
- ? ['"' + v + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
80
- : [v, enumValues.join(', ')]
81
- const target = getTarget(path, k)
82
- errs.push(new cds.error('ASSERT_ENUM', { args, target, statusCode: 400, code: '400' }))
83
- }
84
- }
85
-
86
- const checkRange = (v, ele, errs, path, k) => {
87
- const rangeElements = ele['@assert.range'] && !_getEnumElement(ele) ? ele['@assert.range'] : undefined
88
- if (rangeElements && !_checkInRange(v, rangeElements, resolveCDSType(ele))) {
89
- const args = [v, ...ele['@assert.range']]
90
- const target = getTarget(path, k)
91
- errs.push(new cds.error('ASSERT_RANGE', { args, target, statusCode: 400, code: '400' }))
92
- }
93
- }
94
-
95
- const checkFormat = (v, ele, errs, path, k) => {
96
- const formatElements = ele['@assert.format']
97
- if (formatElements && !_checkRegExpFormat(v, formatElements)) {
98
- const args = [v, formatElements]
99
- const target = getTarget(path, k)
100
- errs.push(new cds.error('ASSERT_FORMAT', { args, target, statusCode: 400, code: '400' }))
101
- }
102
- }
103
-
104
- module.exports = {
105
- checkMandatory,
106
- checkEnum,
107
- checkRange,
108
- checkFormat
109
- }