@sap/cds 6.7.0 → 6.7.1
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 +13 -0
- package/bin/cds.js +4 -2
- package/lib/compile/for/lean_drafts.js +1 -0
- package/libx/_runtime/auth/strategies/mock.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +2 -2
- package/libx/_runtime/cds-services/services/Service.js +27 -0
- package/libx/_runtime/common/generic/auth/utils.js +5 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/fiori/lean-draft.js +45 -27
- package/libx/_runtime/hana/search2cqn4sql.js +1 -1
- package/libx/odata/afterburner.js +4 -4
- package/libx/odata/cqn2odata.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## Version 6.7.1 - 2023-04-14
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Calling a parameterized view without params error now results in the status code 400 with an improved error message.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- cds build error CreateListFromArrayLike
|
|
16
|
+
- Disabling of arbitrary user config in mock auth config using `"users": { "*": false }`
|
|
17
|
+
- Various fixes for `cds.fiori.lean_draft`
|
|
18
|
+
- User attributes that look like numbers are quoted in SQL clause for `@restrict`
|
|
19
|
+
|
|
7
20
|
## Version 6.7.0 - 2023-03-28
|
|
8
21
|
|
|
9
22
|
### Added
|
package/bin/cds.js
CHANGED
|
@@ -14,8 +14,10 @@ const cli = { //NOSONAR
|
|
|
14
14
|
|
|
15
15
|
const task = this.load ('./'+cmd)
|
|
16
16
|
|
|
17
|
-
let args
|
|
18
|
-
try {
|
|
17
|
+
let args = []
|
|
18
|
+
try {
|
|
19
|
+
if (task && cmd !== 'build') args = this.args(task, argv)
|
|
20
|
+
}
|
|
19
21
|
catch (err) { process.exitCode = 1; return console.error(err) }
|
|
20
22
|
|
|
21
23
|
if (task && cmd !== 'build') return task.apply (this, args)
|
|
@@ -108,6 +108,7 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
108
108
|
Object.defineProperty(model.definitions, _draftEntity, { value: draft })
|
|
109
109
|
Object.defineProperty(active, 'drafts', { value: draft })
|
|
110
110
|
Object.defineProperty(draft, 'actives', { value: active })
|
|
111
|
+
Object.defineProperty(draft, 'isDraft', { value: true })
|
|
111
112
|
draft['@cds.persistence.table'] = _draftEntity
|
|
112
113
|
|
|
113
114
|
for (const key in draft) {
|
|
@@ -16,7 +16,7 @@ class MockStrategy {
|
|
|
16
16
|
if (!base64) return this.fail(CHALLENGE)
|
|
17
17
|
|
|
18
18
|
const [id, password] = Buffer.from(base64, 'base64').toString().split(':')
|
|
19
|
-
const user = this.users[id] || ('*'
|
|
19
|
+
const user = this.users[id] || (this.users['*'] && { id })
|
|
20
20
|
if (!user) return this.fail(CHALLENGE)
|
|
21
21
|
if (user.password && user.password !== password) return this.fail(CHALLENGE)
|
|
22
22
|
|
|
@@ -81,7 +81,7 @@ const action = service => {
|
|
|
81
81
|
await tx.rollback(e).catch(() => {})
|
|
82
82
|
}
|
|
83
83
|
} finally {
|
|
84
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
84
|
+
req.messages?.length && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
85
85
|
|
|
86
86
|
if (err) next(err)
|
|
87
87
|
else next(null, toODataResult(result, req))
|
|
@@ -68,7 +68,7 @@ const create = service => {
|
|
|
68
68
|
await tx.rollback(e).catch(() => {})
|
|
69
69
|
}
|
|
70
70
|
} finally {
|
|
71
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
71
|
+
req.messages?.length && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
72
72
|
|
|
73
73
|
if (err) next(err)
|
|
74
74
|
else next(null, toODataResult(result, req))
|
|
@@ -49,7 +49,7 @@ const del = service => {
|
|
|
49
49
|
await tx.rollback(e).catch(() => {})
|
|
50
50
|
}
|
|
51
51
|
} finally {
|
|
52
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
52
|
+
req.messages?.length && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
53
53
|
|
|
54
54
|
if (err) next(err)
|
|
55
55
|
else next(null, null)
|
|
@@ -523,7 +523,7 @@ const read = service => {
|
|
|
523
523
|
await tx.rollback(e).catch(() => {})
|
|
524
524
|
}
|
|
525
525
|
} finally {
|
|
526
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
526
|
+
req.messages?.length && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
527
527
|
|
|
528
528
|
if (err) next(err)
|
|
529
529
|
else next(null, result, additional)
|
|
@@ -179,7 +179,7 @@ const update = service => {
|
|
|
179
179
|
await tx.rollback(e).catch(() => {})
|
|
180
180
|
}
|
|
181
181
|
} finally {
|
|
182
|
-
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
182
|
+
req.messages?.length && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req.http.req))
|
|
183
183
|
|
|
184
184
|
if (err) next(err)
|
|
185
185
|
else if (primitive && result) {
|
|
@@ -223,12 +223,12 @@ const _checkViewWithParamCall = (isView, segments, kind, name) => {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
if (segments.length < 2) {
|
|
226
|
-
throw
|
|
226
|
+
throw cds.error(`Invalid call to "${name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// if the last segment is count, check if previous segment is Set, otherwise check if the last segment equals Set
|
|
230
230
|
if (!_isSet(segments[segments.length - (_isCount(kind) ? 2 : 1)])) {
|
|
231
|
-
throw
|
|
231
|
+
throw cds.error(`Invalid call to "${name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
|
|
@@ -62,6 +62,33 @@ class ApplicationService extends cds.Service {
|
|
|
62
62
|
this.on('EDIT', each, onEdit)
|
|
63
63
|
this.on('CANCEL', each.drafts, onCancel)
|
|
64
64
|
this.on('draftPrepare', each.drafts, onPrepare)
|
|
65
|
+
if (cds.env.fiori.draft_compat) {
|
|
66
|
+
// register after read handlers to add `IsActiveEntity`,
|
|
67
|
+
// so stakeholders have access to it when calling next()
|
|
68
|
+
|
|
69
|
+
// to check if data contains a key value
|
|
70
|
+
let _key
|
|
71
|
+
for (const key in each.keys) {
|
|
72
|
+
if (key === 'IsActiveEntity') continue
|
|
73
|
+
_key = key
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
const _addIsActiveEntity = (data, IsActiveEntity) => {
|
|
77
|
+
if (!data) return
|
|
78
|
+
if (Array.isArray(data)) return data.map(d => _addIsActiveEntity(d, IsActiveEntity))
|
|
79
|
+
if (_key in data) data.IsActiveEntity = IsActiveEntity
|
|
80
|
+
}
|
|
81
|
+
this.on('READ', each, async (_, next) => {
|
|
82
|
+
const data = await next()
|
|
83
|
+
_addIsActiveEntity(data, true)
|
|
84
|
+
return data
|
|
85
|
+
})
|
|
86
|
+
this.on('READ', each, async (_, next) => {
|
|
87
|
+
const data = await next()
|
|
88
|
+
_addIsActiveEntity(data, false)
|
|
89
|
+
return data
|
|
90
|
+
})
|
|
91
|
+
}
|
|
65
92
|
}
|
|
66
93
|
} else return require('../../fiori/generic').impl.call(this)
|
|
67
94
|
}
|
|
@@ -141,8 +141,11 @@ const resolveUserAttrs = (restrict, req) => {
|
|
|
141
141
|
attr = parts.shift()
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (!skip)
|
|
145
|
-
|
|
144
|
+
if (!skip) {
|
|
145
|
+
const v = val === undefined ? null : typeof val === 'string' && val.match(/^\d*$/) ? `'${val}'` : val
|
|
146
|
+
restrict.where = restrict.where.replace(next[0], v).replace('in null', 'is null')
|
|
147
|
+
}
|
|
148
|
+
|
|
146
149
|
next = _getNext(restrict.where)
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -42,6 +42,14 @@ const _inProcessByUserXpr = lockShiftedNow => ({
|
|
|
42
42
|
cast: { type: 'cds.String' }
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
+
/// It's important to wait for the completion of all promises, otherwise a rollback might happen too soon
|
|
46
|
+
const _promiseAll = async array => {
|
|
47
|
+
const results = await Promise.allSettled(array)
|
|
48
|
+
const e = results.find(r => r.status === 'rejected')
|
|
49
|
+
if (e) throw e.reason
|
|
50
|
+
return results.map(r => r.value)
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
const _lock = {
|
|
46
54
|
get shiftedNow() {
|
|
47
55
|
return new Date(Math.max(0, Date.now() - DRAFT_CANCEL_TIMEOUT_IN_MIN() * 60 * 1000)).toISOString()
|
|
@@ -111,15 +119,11 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
111
119
|
_req.validateEtag = req.validateEtag
|
|
112
120
|
const cqnData = _req.query.UPDATE?.data || _req.query.INSERT?.entries?.[0]
|
|
113
121
|
if (cqnData) _req.data = cqnData // must point to the same object
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
proto = Object.getPrototypeOf(proto)
|
|
120
|
-
if (_messagesDescr)
|
|
121
|
-
Object.defineProperty(_req, '_messages', { ..._messagesDescr, get: _messagesDescr.get.bind(req) })
|
|
122
|
-
|
|
122
|
+
Object.defineProperty(_req, '_messages', {
|
|
123
|
+
get: function () {
|
|
124
|
+
return req._messages
|
|
125
|
+
}
|
|
126
|
+
})
|
|
123
127
|
if (req.tx) _req.tx = req.tx
|
|
124
128
|
return _req
|
|
125
129
|
}
|
|
@@ -170,7 +174,7 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
170
174
|
deletes.push(
|
|
171
175
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: draft.DraftAdministrativeData_DraftUUID })
|
|
172
176
|
)
|
|
173
|
-
await
|
|
177
|
+
await _promiseAll(deletes)
|
|
174
178
|
return req.data
|
|
175
179
|
}
|
|
176
180
|
|
|
@@ -203,7 +207,8 @@ cds.ApplicationService.prototype.handle = async function (req) {
|
|
|
203
207
|
delete res.DraftAdministrativeData
|
|
204
208
|
const HasActiveEntity = res.HasActiveEntity
|
|
205
209
|
delete res.HasActiveEntity
|
|
206
|
-
|
|
210
|
+
delete res.IsActiveEntity
|
|
211
|
+
await _promiseAll([
|
|
207
212
|
run(DELETE.from(targetDraft).where(targetWhere)),
|
|
208
213
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: DraftAdministrativeData_DraftUUID })
|
|
209
214
|
])
|
|
@@ -342,7 +347,7 @@ const Read = {
|
|
|
342
347
|
// DraftAdministrativeData is only accessible via drafts
|
|
343
348
|
if (query._target.name.endsWith('.DraftAdministrativeData')) return run(query._drafts)
|
|
344
349
|
if (!query._target._isDraftEnabled) return run(query)
|
|
345
|
-
if (query.SELECT.columns && !query.SELECT.columns.some(c => c === '*')) {
|
|
350
|
+
if (!query.SELECT.groupBy && query.SELECT.columns && !query.SELECT.columns.some(c => c === '*')) {
|
|
346
351
|
const keys = Object_keys(query._target.keys).filter(k => k !== 'IsActiveEntity')
|
|
347
352
|
for (const key of keys) {
|
|
348
353
|
if (!query.SELECT.columns.some(c => c.ref?.[0] === key)) query.SELECT.columns.push({ ref: [key] })
|
|
@@ -382,7 +387,7 @@ const Read = {
|
|
|
382
387
|
draftsQuery.SELECT.columns = keys.map(k => ({ ref: [k] }))
|
|
383
388
|
|
|
384
389
|
const drafts = await run(draftsQuery)
|
|
385
|
-
const res = Read.onlyActives(run, query.where(Read.whereNotIn(query._target, drafts)), {
|
|
390
|
+
const res = await Read.onlyActives(run, query.where(Read.whereNotIn(query._target, drafts)), {
|
|
386
391
|
ignoreDrafts: true
|
|
387
392
|
})
|
|
388
393
|
return _requested(res, query)
|
|
@@ -528,6 +533,7 @@ const Read = {
|
|
|
528
533
|
whereIn: (target, data, not = false) => {
|
|
529
534
|
const keys = Object_keys(target.keys).filter(k => k !== 'IsActiveEntity')
|
|
530
535
|
const dataArray = data ? (Array.isArray(data) ? data : [data]) : []
|
|
536
|
+
if (not && !dataArray.length) return []
|
|
531
537
|
return [
|
|
532
538
|
{ list: keys.map(k => ({ ref: [k] })) },
|
|
533
539
|
not ? 'not in' : 'in',
|
|
@@ -724,9 +730,9 @@ function _cleansed(query, model) {
|
|
|
724
730
|
'=',
|
|
725
731
|
{ val: cds.context.user.id },
|
|
726
732
|
'then',
|
|
727
|
-
|
|
733
|
+
'true',
|
|
728
734
|
'else',
|
|
729
|
-
|
|
735
|
+
'false',
|
|
730
736
|
'end'
|
|
731
737
|
],
|
|
732
738
|
as: 'DraftIsCreatedByMe',
|
|
@@ -747,9 +753,9 @@ function _cleansed(query, model) {
|
|
|
747
753
|
'>',
|
|
748
754
|
{ val: _lock.shiftedNow },
|
|
749
755
|
'then',
|
|
750
|
-
|
|
756
|
+
'true',
|
|
751
757
|
'else',
|
|
752
|
-
|
|
758
|
+
'false',
|
|
753
759
|
'end'
|
|
754
760
|
],
|
|
755
761
|
as: 'DraftIsProcessedByMe',
|
|
@@ -862,7 +868,10 @@ async function onNew(req) {
|
|
|
862
868
|
|
|
863
869
|
// Also support deep insertions
|
|
864
870
|
for (const key in newObj) {
|
|
865
|
-
if (
|
|
871
|
+
if (!target.elements[key]?.isComposition) continue
|
|
872
|
+
if (Array.isArray(newObj[key]))
|
|
873
|
+
newObj[key] = newObj[key].map(v => _assignDraftData(v, target.elements[key]._target))
|
|
874
|
+
else if (typeof newObj[key] === 'object') {
|
|
866
875
|
newObj[key] = _assignDraftData(newObj[key], target.elements[key]._target)
|
|
867
876
|
}
|
|
868
877
|
}
|
|
@@ -875,7 +884,7 @@ async function onNew(req) {
|
|
|
875
884
|
delete draftData.IsActiveEntity
|
|
876
885
|
const draftCQN = INSERT.into(req.target).entries(draftData)
|
|
877
886
|
|
|
878
|
-
await
|
|
887
|
+
await _promiseAll([cds.run(adminDataCQN), this.run(draftCQN)])
|
|
879
888
|
req._.readAfterWrite = true
|
|
880
889
|
return { ...draftData, IsActiveEntity: false }
|
|
881
890
|
}
|
|
@@ -906,20 +915,29 @@ async function onEdit(req) {
|
|
|
906
915
|
}
|
|
907
916
|
}
|
|
908
917
|
_addDraftColumns(req.target, cols)
|
|
909
|
-
|
|
918
|
+
|
|
919
|
+
const existingDraft = SELECT.one(req.target.drafts)
|
|
910
920
|
.columns({ ref: ['DraftAdministrativeData'], expand: [_inProcessByUserXpr(_lock.shiftedNow)] })
|
|
911
921
|
.where(targetWhere)
|
|
912
|
-
.forUpdate({ wait: 0 })
|
|
913
922
|
// prevent service to check for own user
|
|
914
|
-
Object.defineProperty(
|
|
923
|
+
Object.defineProperty(existingDraft, '_draftParams', { value: draftParams, enumerable: false })
|
|
915
924
|
|
|
916
|
-
const activeCQN = SELECT.one.from(req.target).columns(cols).where(targetWhere)
|
|
925
|
+
const activeCQN = SELECT.one.from(req.target).columns(cols).where(targetWhere)
|
|
917
926
|
activeCQN._suppressLocalization = true // in the future we should be able to just set activeCQN.SELECT.localized = false
|
|
918
|
-
|
|
927
|
+
|
|
928
|
+
const activeCheck = SELECT.one(req.target).columns([1]).where(targetWhere).forUpdate()
|
|
929
|
+
Object.defineProperty(activeCheck, '_draftParams', { value: draftParams, enumerable: false })
|
|
930
|
+
// It's not possible to use `FOR UPDATE` in HANA if the view contains joins/unions. Unfortunately, we can't resolve the table entity
|
|
931
|
+
// because we must trigger the app-service request on the target entity (which could be delegated to a remote service).
|
|
932
|
+
// The best we can do is to catch a potential error
|
|
933
|
+
await this.run(activeCheck).catch(_ => {})
|
|
934
|
+
|
|
935
|
+
const [res, draft] = await _promiseAll([
|
|
919
936
|
this.run(activeCQN),
|
|
920
937
|
// no user check must be done here...
|
|
921
|
-
this.run(
|
|
938
|
+
this.run(existingDraft)
|
|
922
939
|
])
|
|
940
|
+
|
|
923
941
|
if (!res) req.reject(404)
|
|
924
942
|
const preserveChanges = req.context?.data?.PreserveChanges
|
|
925
943
|
const inProcessByUser = draft?.DraftAdministrativeData?.InProcessByUser
|
|
@@ -927,7 +945,7 @@ async function onEdit(req) {
|
|
|
927
945
|
if (inProcessByUser || preserveChanges) req.reject(409, 'DRAFT_ALREADY_EXISTS')
|
|
928
946
|
const keys = {}
|
|
929
947
|
for (const key in req.target.drafts.keys) keys[key] = res[key]
|
|
930
|
-
await
|
|
948
|
+
await _promiseAll([
|
|
931
949
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID }),
|
|
932
950
|
this.run(DELETE.from(req.target.drafts).where(keys))
|
|
933
951
|
])
|
|
@@ -983,7 +1001,7 @@ async function onCancel(req) {
|
|
|
983
1001
|
DELETE.from('DRAFT.DraftAdministrativeData').where({ DraftUUID: draft.DraftAdministrativeData_DraftUUID })
|
|
984
1002
|
)
|
|
985
1003
|
if (draftParams.IsActiveEntity) deletes.push(this.run(DELETE.from({ ref: activeRef })))
|
|
986
|
-
await
|
|
1004
|
+
await _promiseAll(deletes)
|
|
987
1005
|
return req.data
|
|
988
1006
|
}
|
|
989
1007
|
|
|
@@ -6,7 +6,7 @@ const { addAliasToExpression } = require('../db/utils/generateAliases')
|
|
|
6
6
|
const targetAlias = 'Target'
|
|
7
7
|
const textsAlias = 'Texts'
|
|
8
8
|
const _generateKeysWhereCondition = (entity, alias1, alias2) => {
|
|
9
|
-
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation)
|
|
9
|
+
const keys = Object.keys(entity.keys).filter(k => !entity.keys[k].isAssociation && !entity.keys[k].virtual)
|
|
10
10
|
const where = []
|
|
11
11
|
keys.forEach(key => {
|
|
12
12
|
if (where.length > 0) where.push('and')
|
|
@@ -266,7 +266,7 @@ function _processSegments(from, model, namespace, cqn) {
|
|
|
266
266
|
ref[i].where = undefined
|
|
267
267
|
if (ref[i + 1] !== 'Set') {
|
|
268
268
|
// /Set is missing
|
|
269
|
-
throw
|
|
269
|
+
throw cds.error(`Invalid call to "${current.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
270
270
|
}
|
|
271
271
|
ref[++i] = null
|
|
272
272
|
} else if (current.kind === 'entity') {
|
|
@@ -372,11 +372,11 @@ function _processSegments(from, model, namespace, cqn) {
|
|
|
372
372
|
const AGGREGATION_DEFAULT = '@Aggregation.default'
|
|
373
373
|
|
|
374
374
|
function _addKeys(columns, target) {
|
|
375
|
-
let hasAggregatedColumn = false,
|
|
375
|
+
let hasAggregatedColumn = false,
|
|
376
|
+
hasStarColumn = false
|
|
376
377
|
for (let k = 0; k < columns.length; k++) {
|
|
377
378
|
if (columns[k] === '*') hasStarColumn = true
|
|
378
379
|
else if (columns[k].func || columns[k].func === null) hasAggregatedColumn = true
|
|
379
|
-
|
|
380
380
|
// Add keys to (sub-)expands
|
|
381
381
|
else if (columns[k].expand && columns[k].expand[0] !== '*')
|
|
382
382
|
_addKeys(columns[k].expand, target.elements[columns[k].ref]._target)
|
|
@@ -444,7 +444,7 @@ const _checkAllKeysProvided = (params, entity) => {
|
|
|
444
444
|
if (isView) {
|
|
445
445
|
// view with params
|
|
446
446
|
if (params === undefined) {
|
|
447
|
-
throw
|
|
447
|
+
throw cds.error(`Invalid call to "${entity.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
448
448
|
} else if (Object.keys(params).length === 0) {
|
|
449
449
|
throw new Error('KEY_EXPECTED')
|
|
450
450
|
}
|
package/libx/odata/cqn2odata.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { formatVal } = require('./utils')
|
|
2
|
+
const cds = require('../_runtime/cds')
|
|
2
3
|
|
|
3
4
|
const OPERATORS = {
|
|
4
5
|
'=': 'eq',
|
|
@@ -253,7 +254,7 @@ function _getQueryTarget(entity, propOrEntity, model) {
|
|
|
253
254
|
|
|
254
255
|
const _params = (args, kind, target) => {
|
|
255
256
|
if (!args) {
|
|
256
|
-
throw
|
|
257
|
+
throw cds.error(`Invalid call to "${target.name}". You need to navigate to Set`, { status: 400, code: 400 })
|
|
257
258
|
}
|
|
258
259
|
const params = Object.keys(args)
|
|
259
260
|
if (params.length !== Object.keys(target.params).length) {
|