@sap/cds 8.3.0 → 8.3.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 +9 -0
- package/lib/compile/etc/_localized.js +1 -0
- package/lib/compile/for/lean_drafts.js +5 -0
- package/lib/linked/validate.js +11 -9
- package/lib/srv/middlewares/cds-context.js +1 -1
- package/libx/_runtime/common/error/frontend.js +18 -4
- package/libx/odata/utils/index.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
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 8.3.1 - 2024-10-08
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Erroneous caching in `cds.validate`
|
|
12
|
+
- Precedence of request headers for `cds.context.id`
|
|
13
|
+
- For `quoted` names, overwrite `@cds.persistence.name` for drafts and localized views properly
|
|
14
|
+
- Do not use hana error code as http status code
|
|
15
|
+
|
|
7
16
|
## Version 8.3.0 - 2024-09-30
|
|
8
17
|
|
|
9
18
|
### Added
|
|
@@ -83,6 +83,7 @@ function unfold_csn (m) { // NOSONAR
|
|
|
83
83
|
function _add_proxy4 (d, name, callback) {
|
|
84
84
|
if (name in m.definitions) return DEBUG && DEBUG ('NOT overriding existing:', name)
|
|
85
85
|
const x = {__proto__:d, name }; DEBUG && DEBUG ('adding proxy:', x)
|
|
86
|
+
if (d['@cds.persistence.name']) x['@cds.persistence.name'] = `localized.${d['@cds.persistence.name']}`
|
|
86
87
|
Object.defineProperty (m.definitions, name, {value:x,writable:true,configurable:true})
|
|
87
88
|
if (callback) callback(x)
|
|
88
89
|
}
|
|
@@ -64,6 +64,11 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
64
64
|
elements: { ...active.elements, ...Draft.elements },
|
|
65
65
|
query: undefined
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
// for quoted names, we need to overwrite the cds.persistence.name of the derived, draft entity
|
|
69
|
+
if (active['@cds.persistence.name'])
|
|
70
|
+
draft['@cds.persistence.name'] = active['@cds.persistence.name'] + '_drafts'
|
|
71
|
+
|
|
67
72
|
Object.defineProperty(model.definitions, _draftEntity, { value: draft })
|
|
68
73
|
Object.defineProperty(active, 'drafts', { value: draft })
|
|
69
74
|
Object.defineProperty(active, 'actives', { value: active })
|
package/lib/linked/validate.js
CHANGED
|
@@ -241,14 +241,16 @@ class Association extends struct {
|
|
|
241
241
|
/** Compositions are like nested entities, validating deep input against their target entity definitions. */
|
|
242
242
|
class Composition extends entity {
|
|
243
243
|
validate (data, path, ctx) { if (!data) return
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
244
|
+
const _validate = this.own('_validate', () => {
|
|
245
|
+
const elements = this._target.elements
|
|
246
|
+
const uplinks = {} // statically determine the uplinks for this composition
|
|
247
|
+
if (this.on) for (let {ref} of this.on) if (ref?.[0] === this.name) {
|
|
248
|
+
const fk = ref[1], fk_ = fk+'_'; uplinks[fk] = true
|
|
249
|
+
for (let e in elements) if (e.startsWith(fk_)) uplinks[e] = true
|
|
250
|
+
}
|
|
251
|
+
return (data, path, ctx) => super.validate (data, path, ctx, elements, uplinks)
|
|
252
|
+
})
|
|
253
|
+
_validate (data, path, ctx)
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
|
|
@@ -312,4 +314,4 @@ $.LargeBinary.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 's
|
|
|
312
314
|
$.LargeString.prototype .type_check = v => Buffer.isBuffer(v) || typeof v === 'string' || v instanceof Readable
|
|
313
315
|
|
|
314
316
|
// Mixin above class extensions to cds.linked.classes
|
|
315
|
-
$.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
|
|
317
|
+
$.mixin ( Decimal, string, $any, action, array, struct, entity, Association, Composition )
|
|
@@ -9,7 +9,7 @@ const { EventContext } = cds
|
|
|
9
9
|
module.exports = () => {
|
|
10
10
|
/** @type { import('express').Handler } */
|
|
11
11
|
return function cds_context (req, res, next) {
|
|
12
|
-
const id = req.headers[corr_id] ??= req.headers[
|
|
12
|
+
const id = req.headers[corr_id] ??= req.headers[crippled_corr_id] || req.headers[req_id] || req.headers[vr_id] || uuid()
|
|
13
13
|
const ctx = EventContext.for ({ id, http: { req, res } })
|
|
14
14
|
res.set ('X-Correlation-ID', id) // Note: we use capitalized style here as that's common standard in HTTP world
|
|
15
15
|
cds._context.run (ctx, next)
|
|
@@ -51,7 +51,7 @@ const _rewriteError = error => {
|
|
|
51
51
|
(code.startsWith('SQLITE_CONSTRAINT') && (message.match(/COMMIT/) || message.match(/FOREIGN KEY/))) ||
|
|
52
52
|
(code === '155' && message.match(/fk constraint violation/))
|
|
53
53
|
) {
|
|
54
|
-
// > foreign key constaint violation
|
|
54
|
+
// > foreign key constaint violation on sqlite/ hana
|
|
55
55
|
error.code = '400'
|
|
56
56
|
error.message = 'FK_CONSTRAINT_VIOLATION'
|
|
57
57
|
return
|
|
@@ -63,17 +63,33 @@ const _rewriteError = error => {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
const _isInHttpResponseCodeRange = errorCode => errorCode >= 300 && errorCode <= 599
|
|
67
|
+
|
|
68
|
+
const BAD_REQUEST_ERRORS = new Set(['ENTITY_ALREADY_EXISTS', 'FK_CONSTRAINT_VIOLATION', 'UNIQUE_CONSTRAINT_VIOLATION'])
|
|
69
|
+
|
|
66
70
|
const _normalize = (err, locale, formatterFn = _getFiltered) => {
|
|
67
71
|
// REVISIT: code and message rewriting
|
|
68
72
|
_rewriteError(err)
|
|
69
73
|
|
|
74
|
+
const { message: originalMessage } = err
|
|
75
|
+
|
|
70
76
|
// message (i18n)
|
|
71
77
|
err.message = getErrorMessage(err, locale)
|
|
72
78
|
|
|
73
79
|
// ensure code is set and a string
|
|
74
80
|
err.code = String(err.code || 'null')
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
// determine status code from error
|
|
83
|
+
let statusCode = err.status || err.statusCode //> REVISIT: why prefer status over statusCode?
|
|
84
|
+
// well-defined bad request errors
|
|
85
|
+
if (!statusCode && BAD_REQUEST_ERRORS.has(originalMessage)) statusCode = 400
|
|
86
|
+
if (!statusCode && _isInHttpResponseCodeRange(err.code)) {
|
|
87
|
+
if ('sqlState' in err) {
|
|
88
|
+
// HANA/ database error -> don't use code as status code
|
|
89
|
+
} else {
|
|
90
|
+
statusCode = err.code
|
|
91
|
+
}
|
|
92
|
+
}
|
|
77
93
|
|
|
78
94
|
// details
|
|
79
95
|
if (err.details) {
|
|
@@ -95,8 +111,6 @@ const _normalize = (err, locale, formatterFn = _getFiltered) => {
|
|
|
95
111
|
return { error, statusCode }
|
|
96
112
|
}
|
|
97
113
|
|
|
98
|
-
const _isAllowedError = errorCode => errorCode >= 300 && errorCode < 505
|
|
99
|
-
|
|
100
114
|
// - for one unique value, we use it
|
|
101
115
|
// - if at least one 5xx exists, we use 500
|
|
102
116
|
// - else if at least one 4xx exists, we use 400
|
|
@@ -278,9 +278,11 @@ const skipToken = (token, cqn) => {
|
|
|
278
278
|
const calculateLocationHeader = (target, srv, result) => {
|
|
279
279
|
const targetName = target.name.replace(`${srv.definition.name}.`, '')
|
|
280
280
|
const filteredKeys = [...target.keys].filter(k => !k.isAssociation).map(k => k.name)
|
|
281
|
+
if (!filteredKeys.every(k => k in result)) return
|
|
282
|
+
|
|
281
283
|
const keyValuePairs = filteredKeys.reduce((acc, key) => {
|
|
282
284
|
const value = result[key]
|
|
283
|
-
if (value === undefined) return
|
|
285
|
+
if (value === undefined) return acc
|
|
284
286
|
if (Buffer.isBuffer(value)) {
|
|
285
287
|
acc[key] = value.toString('base64')
|
|
286
288
|
} else {
|
|
@@ -290,7 +292,6 @@ const calculateLocationHeader = (target, srv, result) => {
|
|
|
290
292
|
}
|
|
291
293
|
return acc
|
|
292
294
|
}, {})
|
|
293
|
-
if (!keyValuePairs) return
|
|
294
295
|
let keys
|
|
295
296
|
const entries = Object.entries(keyValuePairs)
|
|
296
297
|
if (entries.length === 1) {
|