@sap/cds 8.2.1 → 8.2.2
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 +7 -0
- package/lib/linked/validate.js +57 -42
- package/libx/odata/parse/afterburner.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
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.2.2 - 2024-09-13
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Erroneous caching in `cds.validate`
|
|
12
|
+
- Properly check `$filter` element types across navigations
|
|
13
|
+
|
|
7
14
|
## Version 8.2.1 - 2024-09-04
|
|
8
15
|
|
|
9
16
|
### Fixed
|
package/lib/linked/validate.js
CHANGED
|
@@ -92,43 +92,54 @@ const $any = class any {
|
|
|
92
92
|
* this method for subsequent usages, with statically determined checks.
|
|
93
93
|
*/
|
|
94
94
|
check_asserts (val, path, /** @type {Validation} */ ctx) {
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
asserts
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
// IMPORTANT: We need to use this.own() here as elements derived from reuse
|
|
96
|
+
// definitions or from elements of base entities might have different asserts
|
|
97
|
+
// than inherited ones.
|
|
98
|
+
const check_asserts = this.own('_check_asserts', () => {
|
|
99
|
+
const asserts = []
|
|
100
|
+
const type_check = conf.strict && this.strict_check || this.type_check
|
|
101
|
+
if (type_check) {
|
|
102
|
+
asserts.push ((v,p,ctx) => v == null || type_check(v) || ctx.error ('ASSERT_DATA_TYPE', p, this.name, v, this ))
|
|
103
|
+
}
|
|
104
|
+
if (this._is_mandatory()) {
|
|
105
|
+
asserts.push ((v,p,ctx) => v != null && v.trim?.() !== '' || ctx.error ('ASSERT_NOT_NULL', p, this.name, v)) // ASSERT_NOT_NULL is misleading -> should be ASSERT_REQUIRED
|
|
106
|
+
}
|
|
107
|
+
if (this['@assert.format']) {
|
|
108
|
+
const format = new RegExp(this['@assert.format'],'u')
|
|
109
|
+
asserts.push ((v,p,ctx) => v == null || format.test(v) || ctx.error ('ASSERT_FORMAT', p, this.name, v, format))
|
|
110
|
+
}
|
|
111
|
+
if (this['@assert.range'] && !this.enum) {
|
|
112
|
+
const [ min, max ] = this['@assert.range']
|
|
113
|
+
asserts.push ((v,p,ctx) => v == null || min <= v && v <= max || ctx.error ('ASSERT_RANGE', p, this.name, v, min, max))
|
|
114
|
+
}
|
|
115
|
+
if (this['@assert.enum'] || this['@assert.range'] && this.enum) {
|
|
116
|
+
const vals = Object.entries(this.enum).map(([k,v]) => 'val' in v ? v.val : k)
|
|
117
|
+
const enums = vals.reduce((a,v) => (a[v]=true, a),{})
|
|
118
|
+
asserts.push ((v,p,ctx) => v == null || v in enums || vals.some(x => x == v) || ctx.error ('ASSERT_ENUM', p, this.name, typeof v === 'string' ? `"${v}"` : v, vals.join(', ')))
|
|
119
|
+
}
|
|
120
|
+
if (!asserts.length) return ()=>{} // nothing to do
|
|
121
|
+
return (v,p,ctx) => asserts.forEach (a => a(v,p,ctx))
|
|
122
|
+
})
|
|
123
|
+
return check_asserts (val, path, ctx)
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
_is_mandatory (d=this) {
|
|
123
|
-
return d.own('_mandatory', ()=>
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
return d.own('_mandatory', ()=> {
|
|
128
|
+
if (d._is_readonly()) return false // readonly annotations have precedence over mandatory ones
|
|
129
|
+
if (d['@mandatory'] || d['@Common.FieldControl']?.['#'] === 'Mandatory') {
|
|
130
|
+
const q = d.parent?.query?.SELECT
|
|
131
|
+
if (!q) return true // it's a regular entity's element marked as mandatory
|
|
132
|
+
if (!q.from?.ref) return false // join or union -> elements can't be mandatory
|
|
133
|
+
const c = q.columns?.find (c => alias4(c) === d.name)
|
|
134
|
+
if (!c) return true // * or foo.* -> can't tell whether d is joined
|
|
135
|
+
if (!c.ref) return false // calculated fields aren't mandatory
|
|
136
|
+
if (c.ref.length === 1) return true // SELECT from Foo { foo }
|
|
137
|
+
if (c.ref.length === 2 && c.ref[0] === alias4(q.from)) return true // SELECT from Foo as f { f.foo }
|
|
138
|
+
else return false // joined field which can't be mandatory, e.g. SELECT from Books { author.name as author }
|
|
139
|
+
function alias4 (x) { return x.as || x.ref?.at(-1) }
|
|
140
|
+
}
|
|
141
|
+
else return false
|
|
142
|
+
})
|
|
132
143
|
}
|
|
133
144
|
|
|
134
145
|
_is_readonly (d=this) {
|
|
@@ -148,17 +159,21 @@ const $any = class any {
|
|
|
148
159
|
* This is the case if the row date does not contain all primary key elements of the target entity.
|
|
149
160
|
*/
|
|
150
161
|
_is_insert (row) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
// IMPORTANT: We need to use this.own() here as derived entities might have
|
|
163
|
+
// different keys and thus different insert checks.
|
|
164
|
+
const _is_insert = this.own('__is_insert', () => {
|
|
165
|
+
const entity = this._target || this
|
|
166
|
+
const keys = Object.keys (entity.keys||{})
|
|
167
|
+
if (!keys.length) return ()=> true
|
|
168
|
+
else return data => typeof data === 'object' && !keys.every(k => k in data)
|
|
169
|
+
})
|
|
170
|
+
return _is_insert(row)
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
_required (elements) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
174
|
+
// IMPORTANT: We need to use this.own() here as derived entities might have
|
|
175
|
+
// different elements or elements with different annotations than base entitites.
|
|
176
|
+
return this.own('__required', ()=> Object.values(elements).filter(this._is_mandatory))
|
|
162
177
|
}
|
|
163
178
|
|
|
164
179
|
/** Forward declaration for universal CSN */
|