@sap/cds 7.9.3 → 8.0.3

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 (278) hide show
  1. package/CHANGELOG.md +126 -3655
  2. package/_i18n/i18n_en_US_saptrc.properties +113 -0
  3. package/_i18n/i18n_zh_CN.properties +7 -4
  4. package/app/index.css +129 -0
  5. package/app/index.html +16 -64
  6. package/app/index.js +14 -9
  7. package/bin/args.js +34 -0
  8. package/bin/serve.js +18 -24
  9. package/bin/test.js +97 -0
  10. package/common.cds +5 -12
  11. package/eslint.config.mjs +133 -0
  12. package/lib/auth/basic-auth.js +16 -20
  13. package/lib/auth/dummy-auth.js +1 -1
  14. package/lib/auth/ias-auth.js +9 -41
  15. package/lib/auth/index.js +1 -14
  16. package/lib/auth/jwt-auth.js +10 -40
  17. package/lib/compile/cds-compile.js +1 -2
  18. package/lib/compile/cdsc.js +21 -26
  19. package/lib/compile/etc/_localized.js +1 -6
  20. package/lib/compile/etc/csv.js +1 -1
  21. package/lib/compile/etc/properties.js +1 -1
  22. package/lib/compile/for/java.js +1 -1
  23. package/lib/compile/for/lean_drafts.js +4 -6
  24. package/lib/compile/for/nodejs.js +1 -1
  25. package/lib/compile/parse.js +4 -0
  26. package/lib/compile/resolve.js +4 -4
  27. package/lib/compile/to/edm-files.js +16 -23
  28. package/lib/compile/to/hana.js +27 -0
  29. package/lib/compile/to/json.js +1 -1
  30. package/lib/compile/to/sql.js +5 -1
  31. package/lib/compile/to/yaml.js +3 -3
  32. package/lib/dbs/cds-deploy.js +4 -2
  33. package/lib/env/cds-env.js +10 -14
  34. package/lib/env/cds-requires.js +29 -13
  35. package/lib/env/defaults.js +46 -16
  36. package/lib/env/plugins.js +1 -1
  37. package/lib/env/schemas/cds-rc.js +8 -4
  38. package/lib/env/schemas/index.js +7 -7
  39. package/lib/env/serviceBindings.js +1 -1
  40. package/lib/index.js +12 -10
  41. package/lib/lazy.js +1 -1
  42. package/lib/linked/classes.js +36 -8
  43. package/lib/linked/entities.js +2 -10
  44. package/lib/linked/models.js +2 -1
  45. package/lib/linked/validate.js +292 -0
  46. package/lib/log/cds-error.js +0 -6
  47. package/lib/log/cds-log.js +3 -3
  48. package/lib/log/format/json.js +1 -1
  49. package/lib/log/service/index.js +0 -1
  50. package/lib/plugins.js +2 -2
  51. package/lib/ql/Query.js +2 -10
  52. package/lib/ql/SELECT.js +1 -1
  53. package/lib/ql/Whereable.js +3 -2
  54. package/lib/req/cds-context.js +14 -25
  55. package/lib/req/context.js +23 -25
  56. package/lib/req/request.js +1 -34
  57. package/lib/req/user.js +47 -35
  58. package/lib/srv/bindings.js +1 -1
  59. package/lib/srv/cds-connect.js +4 -4
  60. package/lib/srv/cds-serve.js +2 -2
  61. package/lib/srv/factory.js +1 -1
  62. package/lib/srv/middlewares/cds-context.js +11 -22
  63. package/lib/srv/middlewares/ctx-model.js +2 -3
  64. package/lib/srv/middlewares/errors.js +41 -8
  65. package/lib/srv/middlewares/index.js +3 -3
  66. package/lib/srv/middlewares/trace.js +0 -2
  67. package/lib/srv/protocols/hcql.js +15 -10
  68. package/lib/srv/protocols/http.js +44 -49
  69. package/lib/srv/protocols/index.js +1 -23
  70. package/lib/srv/protocols/odata-v4.js +12 -74
  71. package/lib/srv/protocols/rest.js +1 -13
  72. package/lib/srv/srv-api.js +0 -20
  73. package/lib/srv/srv-dispatch.js +3 -2
  74. package/lib/srv/srv-handlers.js +22 -11
  75. package/lib/srv/srv-methods.js +2 -2
  76. package/lib/srv/srv-models.js +3 -36
  77. package/lib/test/expect.js +343 -0
  78. package/lib/test/index.js +2 -0
  79. package/lib/test/reporter.js +176 -0
  80. package/lib/utils/axios.js +10 -9
  81. package/lib/utils/cds-test.js +85 -36
  82. package/lib/utils/cds-utils.js +54 -7
  83. package/lib/utils/check-version.js +0 -4
  84. package/lib/utils/colors.js +49 -0
  85. package/lib/utils/data.js +5 -4
  86. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +2 -7
  87. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +3 -30
  88. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +6 -12
  89. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +1 -3
  90. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +0 -1
  91. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +4 -7
  92. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +12 -6
  93. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +2 -4
  94. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/applyToCQN.js +1 -0
  95. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +1 -1
  96. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/index.js +0 -1
  97. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +1 -3
  98. package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +1 -1
  99. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +1 -2
  100. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +5 -0
  101. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
  102. package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +9 -43
  103. package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +0 -1
  104. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +8 -3
  105. package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +4 -2
  106. package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -3
  107. package/libx/_runtime/cds-services/util/assert.js +1 -1
  108. package/libx/_runtime/cds.js +10 -3
  109. package/libx/_runtime/common/Service.js +12 -32
  110. package/libx/_runtime/common/aspects/any.js +1 -0
  111. package/libx/_runtime/common/code-ext/execute.js +1 -1
  112. package/libx/_runtime/common/code-ext/worker.js +0 -1
  113. package/libx/_runtime/common/composition/data.js +0 -1
  114. package/libx/_runtime/common/composition/delete.js +0 -1
  115. package/libx/_runtime/common/composition/insert.js +2 -2
  116. package/libx/_runtime/common/composition/tree.js +0 -1
  117. package/libx/_runtime/common/composition/update.js +3 -3
  118. package/libx/_runtime/common/error/frontend.js +21 -12
  119. package/libx/_runtime/common/error/log.js +36 -0
  120. package/libx/_runtime/common/error/utils.js +2 -5
  121. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  122. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  123. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  124. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  125. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  126. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  127. package/libx/_runtime/common/generic/crud.js +6 -5
  128. package/libx/_runtime/common/generic/etag.js +7 -12
  129. package/libx/_runtime/common/generic/input.js +70 -68
  130. package/libx/_runtime/common/generic/paging.js +1 -0
  131. package/libx/_runtime/common/generic/sorting.js +1 -0
  132. package/libx/_runtime/common/generic/temporal.js +8 -2
  133. package/libx/_runtime/common/i18n/index.js +1 -1
  134. package/libx/_runtime/common/i18n/messages.properties +3 -1
  135. package/libx/_runtime/common/utils/binary.js +8 -2
  136. package/libx/_runtime/common/utils/compareJson.js +5 -1
  137. package/libx/_runtime/common/utils/copy.js +6 -11
  138. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  139. package/libx/_runtime/common/utils/differ.js +3 -6
  140. package/libx/_runtime/common/utils/keys.js +77 -18
  141. package/libx/_runtime/common/utils/postProcess.js +12 -15
  142. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  143. package/libx/_runtime/common/utils/resolveView.js +2 -3
  144. package/libx/_runtime/common/utils/restrictions.js +45 -17
  145. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  146. package/libx/_runtime/common/utils/stream.js +3 -16
  147. package/libx/_runtime/common/utils/streamProp.js +8 -18
  148. package/libx/_runtime/common/utils/structured.js +1 -1
  149. package/libx/_runtime/common/utils/ucsn.js +0 -2
  150. package/libx/_runtime/db/Service.js +0 -72
  151. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  152. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  153. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  154. package/libx/_runtime/db/generic/input.js +3 -8
  155. package/libx/_runtime/db/generic/rewrite.js +1 -0
  156. package/libx/_runtime/db/query/read.js +2 -2
  157. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  158. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  159. package/libx/_runtime/db/utils/columns.js +2 -6
  160. package/libx/_runtime/fiori/lean-draft.js +138 -56
  161. package/libx/_runtime/hana/Service.js +0 -1
  162. package/libx/_runtime/hana/driver.js +1 -1
  163. package/libx/_runtime/hana/dynatrace.js +1 -2
  164. package/libx/_runtime/hana/pool.js +11 -21
  165. package/libx/_runtime/hana/streaming.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  167. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  168. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  170. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  171. package/libx/_runtime/messaging/event-broker.js +0 -12
  172. package/libx/_runtime/messaging/file-based.js +3 -3
  173. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  174. package/libx/_runtime/messaging/kafka.js +2 -2
  175. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  176. package/libx/_runtime/remote/Service.js +25 -25
  177. package/libx/_runtime/remote/utils/client.js +4 -5
  178. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  179. package/libx/_runtime/remote/utils/data.js +0 -1
  180. package/libx/_runtime/sqlite/Service.js +1 -2
  181. package/libx/_runtime/ucl/Service.js +37 -78
  182. package/libx/common/assert/index.js +22 -21
  183. package/libx/common/assert/type-relaxed.js +39 -0
  184. package/libx/common/assert/utils.js +3 -2
  185. package/libx/common/assert/validation.js +3 -8
  186. package/libx/common/utils/index.js +5 -0
  187. package/libx/common/utils/path.js +51 -0
  188. package/libx/odata/ODataAdapter.js +126 -0
  189. package/libx/odata/index.js +15 -2
  190. package/libx/odata/middleware/batch.js +261 -72
  191. package/libx/odata/middleware/body-parser.js +33 -0
  192. package/libx/odata/middleware/create.js +44 -59
  193. package/libx/odata/middleware/delete.js +23 -12
  194. package/libx/odata/middleware/error.js +30 -6
  195. package/libx/odata/middleware/metadata.js +38 -26
  196. package/libx/odata/middleware/operation.js +93 -69
  197. package/libx/odata/middleware/parse.js +6 -8
  198. package/libx/odata/middleware/read.js +117 -93
  199. package/libx/odata/middleware/service-document.js +22 -19
  200. package/libx/odata/middleware/stream.js +54 -56
  201. package/libx/odata/middleware/update.js +79 -87
  202. package/libx/odata/parse/afterburner.js +191 -175
  203. package/libx/odata/parse/cqn2odata.js +8 -8
  204. package/libx/odata/parse/grammar.peggy +27 -20
  205. package/libx/odata/parse/multipartToJson.js +17 -9
  206. package/libx/odata/parse/parser.js +1 -1
  207. package/libx/odata/utils/etag.js +14 -6
  208. package/libx/odata/utils/index.js +84 -12
  209. package/libx/odata/utils/metadata.js +161 -0
  210. package/libx/odata/utils/postProcess.js +89 -0
  211. package/libx/odata/utils/readAfterWrite.js +134 -17
  212. package/libx/odata/utils/result.js +36 -142
  213. package/libx/outbox/index.js +4 -3
  214. package/libx/rest/RestAdapter.js +115 -182
  215. package/libx/rest/middleware/create.js +28 -24
  216. package/libx/rest/middleware/delete.js +7 -10
  217. package/libx/rest/middleware/error.js +19 -16
  218. package/libx/rest/middleware/operation.js +48 -41
  219. package/libx/rest/middleware/parse.js +128 -126
  220. package/libx/rest/middleware/read.js +20 -27
  221. package/libx/rest/middleware/update.js +26 -31
  222. package/package.json +17 -8
  223. package/server.js +4 -2
  224. package/tasks/enterprise-messaging-deploy.js +1 -1
  225. package/apis/cds.d.ts +0 -3
  226. package/apis/core.d.ts +0 -21
  227. package/apis/cqn.d.ts +0 -18
  228. package/apis/csn.d.ts +0 -21
  229. package/apis/events.d.ts +0 -18
  230. package/apis/internal/inference.d.ts +0 -18
  231. package/apis/linked.d.ts +0 -18
  232. package/apis/log.d.ts +0 -20
  233. package/apis/models.d.ts +0 -18
  234. package/apis/ql.d.ts +0 -18
  235. package/apis/reflect.d.ts +0 -32
  236. package/apis/server.d.ts +0 -18
  237. package/apis/services.d.ts +0 -22
  238. package/bin/cds-serve.js +0 -56
  239. package/lib/compile/to/gql.js +0 -15
  240. package/lib/srv/protocols/_legacy.js +0 -44
  241. package/lib/utils/jest.js +0 -43
  242. package/libx/_runtime/auth/index.js +0 -193
  243. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  244. package/libx/_runtime/auth/strategies/basic.js +0 -20
  245. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  246. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  247. package/libx/_runtime/auth/strategies/mock.js +0 -77
  248. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  249. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  250. package/libx/_runtime/common/perf/index.js +0 -19
  251. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  252. package/libx/_runtime/fiori/draft.js +0 -2
  253. package/libx/_runtime/fiori/generic/activate.js +0 -190
  254. package/libx/_runtime/fiori/generic/before.js +0 -201
  255. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  256. package/libx/_runtime/fiori/generic/delete.js +0 -21
  257. package/libx/_runtime/fiori/generic/edit.js +0 -157
  258. package/libx/_runtime/fiori/generic/index.js +0 -25
  259. package/libx/_runtime/fiori/generic/new.js +0 -82
  260. package/libx/_runtime/fiori/generic/patch.js +0 -101
  261. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  262. package/libx/_runtime/fiori/generic/read.js +0 -1340
  263. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  264. package/libx/_runtime/fiori/utils/csn.js +0 -13
  265. package/libx/_runtime/fiori/utils/delete.js +0 -114
  266. package/libx/_runtime/fiori/utils/handler.js +0 -264
  267. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  268. package/libx/_runtime/fiori/utils/req.js +0 -23
  269. package/libx/_runtime/fiori/utils/stream.js +0 -36
  270. package/libx/_runtime/fiori/utils/where.js +0 -254
  271. package/libx/_runtime/index.js +0 -22
  272. package/libx/odata/utils/handler.js +0 -120
  273. package/libx/odata/utils/metaInfo.js +0 -410
  274. package/libx/odata/utils/path.js +0 -75
  275. package/libx/rest/RestRequest.js +0 -32
  276. package/libx/rest/index.js +0 -3
  277. package/libx/rest/readme.md +0 -1
  278. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,10 +1,11 @@
1
1
  const { cds } = global
2
2
 
3
- const typeCheckers = require('./type')
4
- const { checkMandatory, checkEnum, checkRange, checkFormat } = require('./validation')
5
- const { getNested, getNormalizedDecimal, getTarget, resolveCDSType, resolveSegment } = require('./utils')
3
+ const strictTypeCheckers = require('./type-strict')
4
+ const relaxedTypeCheckers = require('./type-relaxed')
5
+ let typeCheckers
6
6
 
7
- const NUMBER_TYPES = new Set(['cds.UInt8', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Double'])
7
+ const { checkMandatory, checkEnum, checkRange, checkFormat } = require('./validation')
8
+ const { getNested, getTarget, resolveCDSType, resolveSegment } = require('./utils')
8
9
 
9
10
  const _no_op = () => {}
10
11
 
@@ -91,6 +92,10 @@ function _process(obj, def, errs, opts) {
91
92
  continue
92
93
  }
93
94
 
95
+ if (ele['@cds.api.ignore']) {
96
+ opts._handle_unknown(obj, k, def, errs)
97
+ continue
98
+ }
94
99
  if (ele.isAssociation) {
95
100
  const keys = ele.keys?.map(k => k.ref[0]) || Object.keys(ele._target.keys)
96
101
  opts.path.push(ele.is2many || Object.keys(keys).length ? { assoc: k, keys } : k)
@@ -114,7 +119,13 @@ function _process(obj, def, errs, opts) {
114
119
  opts.path.pop()
115
120
  continue
116
121
  }
117
- if (ele instanceof cds.builtin.classes.array) {
122
+ if (ele instanceof cds.builtin.classes.array && v !== undefined) {
123
+ if (!Array.isArray(v)) {
124
+ // REVISIT: allow null if served via REST or OData v4.02
125
+ const target = getTarget(opts.path, k)
126
+ errs.push(new cds.error('ASSERT_ARRAY', { target, statusCode: 400, code: '400' }))
127
+ continue
128
+ }
118
129
  for (let i = 0; i < v.length; i++) {
119
130
  opts.path.push({ prop: k, index: i })
120
131
  const _def = ele.items?.__proto__.elements ? ele.items.__proto__ : ele.__proto__
@@ -138,26 +149,14 @@ function _process(obj, def, errs, opts) {
138
149
  if (typeChecker) {
139
150
  if (v == null) continue
140
151
 
141
- // if used in protocol adapter, adjust val/ checker if necessary
142
- if (opts.http) {
143
- if (typeof v !== 'boolean') {
144
- if (type === 'cds.Decimal') v = getNormalizedDecimal(v)
145
- else if (type === 'cds.Int64') v = String(v)
146
- else if (NUMBER_TYPES.has(type)) v = Number(v)
147
- }
148
- }
149
-
150
- // use relaxed uuid check if not in strict mode
151
- if (type === 'cds.UUID' && !opts.strict) typeChecker = typeCheckers['relaxed.UUID']
152
-
153
152
  // type check
154
153
  // REVISIT: all checkers should add errors themselves!
155
- if (type === 'cds.Decimal')
154
+ if (type === 'cds.Decimal' && opts.strict) {
156
155
  typeChecker(v, ele, errs, opts.path, k) //> _checkDecimal adds error itself
157
- else if (!typeChecker(v, ele) || (opts.strict && typeChecker.name === '_checkBuffer' && typeof v === 'string')) {
156
+ } else if (!typeChecker(v, ele)) {
158
157
  errs.push(
159
158
  new cds.error('ASSERT_DATA_TYPE', {
160
- args: [typeof obj[k] === 'string' ? `"${obj[k]}"` : obj[k], ele._type],
159
+ args: [typeof v === 'string' ? `"${v}"` : v, ele._type],
161
160
  target: getTarget(opts.path, k),
162
161
  statusCode: 400,
163
162
  code: '400'
@@ -169,7 +168,7 @@ function _process(obj, def, errs, opts) {
169
168
  if (obj[k] !== v) obj[k] = v
170
169
 
171
170
  // @assert
172
- if (ele['@assert.enum'] || (ele['@assert.range'] && ele.enum)) checkEnum(v, ele, errs, opts.path, k)
171
+ if (ele['@assert.range'] && ele.enum) checkEnum(v, ele, errs, opts.path, k)
173
172
  if (ele['@assert.range']) checkRange(v, ele, errs, opts.path, k)
174
173
  if (ele['@assert.format']) checkFormat(v, ele, errs, opts.path, k)
175
174
  // REVISIT: @assert.target? -> no because async, but maybe return the necessary query to execute?
@@ -212,6 +211,8 @@ module.exports = (data, definition, options = {}) => {
212
211
  options.path ??= []
213
212
 
214
213
  // materialize what is done ...
214
+ // ... re type checks
215
+ typeCheckers = options.strict ? strictTypeCheckers : relaxedTypeCheckers
215
216
  // ... in case of unknown elements
216
217
  if (options.strict) options._handle_unknown = _reject_unknown
217
218
  else if (options.filter) options._handle_unknown = _filter_unknown
@@ -0,0 +1,39 @@
1
+ const { Readable } = require('stream')
2
+
3
+ const _isString = v => typeof v === 'string'
4
+
5
+ const _isBoolean = v => typeof v === 'boolean'
6
+
7
+ const _isNumber = v => typeof v === 'number'
8
+
9
+ const _isNumberish = v => !!Number(v) || v === 0 //> string representation of number is ok, e.g., '1e3'
10
+
11
+ const _isDate = v => !!Date.parse(v)
12
+
13
+ const _isTime = v => !!Date.parse('2000-01-01T' + v)
14
+
15
+ const _isBuffer = v => Buffer.isBuffer(v) || v.type === 'Buffer' || _isString(v) //> base64 encoded string is ok as well
16
+
17
+ const _isStreamOrBuffer = v => v instanceof Readable || _isBuffer(v)
18
+
19
+ module.exports = {
20
+ 'cds.UUID': _isString,
21
+ 'cds.Boolean': _isBoolean,
22
+ 'cds.Integer': _isNumber,
23
+ 'cds.UInt8': _isNumber,
24
+ 'cds.Int16': _isNumber,
25
+ 'cds.Int32': _isNumber,
26
+ 'cds.Integer64': _isNumberish,
27
+ 'cds.Int64': _isNumberish,
28
+ 'cds.Decimal': _isNumberish,
29
+ 'cds.DecimalFloat': _isNumber,
30
+ 'cds.Double': _isNumber,
31
+ 'cds.Date': _isDate,
32
+ 'cds.Time': _isTime,
33
+ 'cds.DateTime': _isDate,
34
+ 'cds.Timestamp': _isDate,
35
+ 'cds.String': _isString,
36
+ 'cds.Binary': _isBuffer,
37
+ 'cds.LargeString': _isString,
38
+ 'cds.LargeBinary': _isStreamOrBuffer
39
+ }
@@ -102,8 +102,9 @@ function resolveSegment(prev, obj, def) {
102
102
  keys.push(`${k}=false`) //> always false if not in obj as it must be a draft activate
103
103
  else keys.push(`${k}=null`)
104
104
  } else {
105
- const type = resolveCDSType(def.elements[k])
106
- if (type === 'cds.String') val = `'${val}'`
105
+ const cdsType = resolveCDSType(def.elements[k])
106
+ const odataType = def.elements[k]['@odata.Type']
107
+ if (!odataType && cdsType === 'cds.String' || odataType === 'Edm.String') val = `'${val}'`
107
108
  // TODO: more proper val encoding based on type
108
109
  keys.push(`${k}=${val}`)
109
110
  }
@@ -6,7 +6,7 @@ const {
6
6
  'cds.DateTime': checkISODateTime,
7
7
  'cds.Timestamp': checkISOTimestamp,
8
8
  'cds.String': checkString
9
- } = require('./type')
9
+ } = require('./type-strict')
10
10
  const { getTarget, resolveCDSType } = require('./utils')
11
11
 
12
12
  const _isNavigationColumn = (col, as) => col.ref?.length > 1 && (col.as === as || col.ref[col.ref.length - 1] === as)
@@ -14,7 +14,7 @@ const _isNavigationColumn = (col, as) => col.ref?.length > 1 && (col.as === as |
14
14
  // REVISIT: mandatory is actually not the same as not null or empty string
15
15
  const _isNotFilled = val => val === null || val === undefined || (typeof val === 'string' && val.trim() === '')
16
16
 
17
- const _getEnumElement = ele => ((ele['@assert.range'] && ele.enum) || ele['@assert.enum'] ? ele.enum : undefined)
17
+ const _getEnumElement = ele => ((ele['@assert.range'] && ele.enum) ? ele.enum : undefined)
18
18
 
19
19
  const _enumValues = ele => {
20
20
  return Object.keys(ele).map(enumKey => {
@@ -73,12 +73,7 @@ const checkMandatory = (v, ele, errs, path, k) => {
73
73
  const checkEnum = (v, ele, errs, path, k) => {
74
74
  const enumElements = _getEnumElement(ele)
75
75
  const enumValues = enumElements && _enumValues(enumElements)
76
- const includes = (enumValues, v) => {
77
- if (ele._type in { 'cds.Decimal': 1, 'cds.Int64': 1 }) {
78
- return enumValues.map(ev => String(ev)).includes(String(v))
79
- } else return enumValues.includes(v)
80
- }
81
- if (enumElements && !includes(enumValues, v)) {
76
+ if (enumElements && !enumValues.some(ev => ev == v)) { //> use == for automatic type coercion
82
77
  const args =
83
78
  typeof v === 'string'
84
79
  ? ['"' + v + '"', enumValues.map(ele => '"' + ele + '"').join(', ')]
@@ -0,0 +1,5 @@
1
+ const { getKeysAndParamsFromPath } = require('./path')
2
+
3
+ module.exports = {
4
+ getKeysAndParamsFromPath
5
+ }
@@ -0,0 +1,51 @@
1
+ const cds = require('../../_runtime/cds')
2
+ const { where2obj } = require('../../_runtime/common/utils/cqn')
3
+ const { getKeysForNavigationFromRefPath } = require('../../_runtime/common/utils/keys')
4
+
5
+ // REVISIT: do we already have something like this _without using okra api_?
6
+ // REVISIT: should we still support process.env.CDS_FEATURES_PARAMS? probably nobody uses it...
7
+ const getKeysAndParamsFromPath = (from, srv) => {
8
+ if (!from.ref || !from.ref.length) return {}
9
+
10
+ const keys = {}
11
+ const params = []
12
+
13
+ const model = cds.context.model ?? srv.model
14
+
15
+ let cur = model.definitions
16
+ let lastElement
17
+
18
+ for (let i = 0; i < from.ref.length; i++) {
19
+ const ref = from.ref[i]
20
+ const id = ref.id || ref
21
+ lastElement = cur[id]
22
+ const target = cur[id]._target ?? lastElement
23
+ cur = target.elements
24
+
25
+ if (i === from.ref.length - 1) {
26
+ // last element
27
+ if (ref.where) {
28
+ const seg_keys = where2obj(ref.where)
29
+ Object.assign(keys, seg_keys)
30
+ params[i] = seg_keys.ID && Object.keys(seg_keys).length === 1 ? seg_keys.ID : seg_keys
31
+ }
32
+ if (lastElement.isAssociation && from.ref.length > 1) {
33
+ // add keys for navigation from path
34
+ const rootRef = from.ref[0]
35
+ const rootTarget = model.definitions[rootRef.id || rootRef]
36
+ const seg_keys = getKeysForNavigationFromRefPath(from.ref, rootTarget)
37
+ // only take if a known property
38
+ for (const k in seg_keys) if (k in cur) keys[k] = seg_keys[k]
39
+ }
40
+ } else if (ref.where) {
41
+ const seg_keys = where2obj(ref.where)
42
+ params[i] = seg_keys.ID && Object.keys(seg_keys).length === 1 ? seg_keys.ID : seg_keys
43
+ }
44
+ }
45
+
46
+ return { keys, params }
47
+ }
48
+
49
+ module.exports = {
50
+ getKeysAndParamsFromPath
51
+ }
@@ -0,0 +1,126 @@
1
+ const HttpAdapter = require('../../lib/srv/protocols/http')
2
+ const cds = require('../../lib')
3
+ const LOG = cds.log('odata')
4
+
5
+ const operation4 = require('./middleware/operation')
6
+ const create4 = require('./middleware/create')
7
+ const stream4 = require('./middleware/stream')
8
+ const read4 = require('./middleware/read')
9
+ const update4 = require('./middleware/update')
10
+ const delete4 = require('./middleware/delete')
11
+ const error4 = require('./middleware/error')
12
+ const bodyParser4 = require('./middleware/body-parser')
13
+
14
+ // REVISIT: copied from lib/req/request.js
15
+ const Http2Crud = { POST: 'CREATE', GET: 'READ', PUT: 'UPDATE', PATCH: 'UPDATE', DELETE: 'DELETE' }
16
+
17
+ const { isStream } = require('./utils')
18
+
19
+ class ODataAdapter extends HttpAdapter {
20
+ log(req) {
21
+ // REVISIT: this impl recreates the behavior of the old adapter, but is not very clean
22
+
23
+ // req.__proto__.method is set in case of upsert
24
+ if (req.__proto__.method in { PUT: 1, PATCH: 1 }) return // REVISIT: voodoo magic
25
+
26
+ if (req._subrequest)
27
+ //> req._subrequest is set for batch subrequests
28
+ LOG._info && LOG.info('>', Http2Crud[req.method], req.path, Object.keys(req.query).length ? { ...req.query } : '')
29
+ else super.log(req)
30
+ }
31
+
32
+ // early_access_check4(srv) { // REVISIT: let's remove that!
33
+ // const super_early_access_check = super.early_access_check4(srv)
34
+ // // REVISIT: We should remove the protectMetadata option in cds8, and always just do the right thing -> DOUBLE CHECK: Bad fiori behavior on 403ers
35
+ // return function early_access_check(req, res, next) {
36
+ // if (cds.env.odata.protectMetadata === false && (req.path === '/' || req.path === '/$metadata')) { // REVISIT: do that statically!
37
+ // // > nothing to do
38
+ // return next()
39
+ // }
40
+
41
+ // // REVISIT: Why exactly was that required?
42
+ // // > It was wrong, as it requested a login for any service operation, even though only one action was restricted (submitOrder)
43
+ // // we need to challenge in case of $batch requests if the service has restrictions
44
+ // // const user = cds.context.user
45
+ // // if (user._is_anonymous && req.path === '/$batch' && containsAnyRestrictions(srv)) {
46
+ // // if (!req._login) throw cds.error({ code: '401', statusCode: 401 })
47
+ // // return req._login()
48
+ // // }
49
+
50
+ // return super_early_access_check(req, res, next)
51
+ // }
52
+ // }
53
+
54
+ get router() {
55
+ const jsonBodyParser = bodyParser4(this)
56
+ return (
57
+ super.router
58
+ .use(function odata_version(req, res, next) {
59
+ res.set('OData-Version', '4.0')
60
+ next()
61
+ })
62
+ // REVISIT: add middleware for negative cases?
63
+ // service root
64
+ .use(/^\/$/, require('./middleware/service-document')(this))
65
+ .use('/\\$metadata', require('./middleware/metadata')(this))
66
+ // parse
67
+ .use(require('./middleware/parse')(this))
68
+ .use(function odata_streams(req, res, next) {
69
+ if (req.method === 'PUT' && isStream(req._query)) {
70
+ req.body = { value: req }
71
+ return next()
72
+ }
73
+ if (req.method === 'POST' && req.headers['content-type']?.match(/multipart\/mixed/)) {
74
+ return next()
75
+ }
76
+ if (req.method in { POST: 1, PUT: 1, PATCH: 1 } && req.headers['content-type']) {
77
+ const parts = req.headers['content-type'].split(';')
78
+ // header ending with semicolon is not allowed
79
+ if (!parts[0].match(/^application\/json$/) || parts[1] === '') {
80
+ throw cds.error('415', { statusCode: 415, code: '415' }) // FIXME: use res.status
81
+ }
82
+ }
83
+ // POST with empty body is allowed by actions
84
+ if (req.method in { PUT: 1, PATCH: 1 }) {
85
+ if (req.headers['content-length'] === '0') {
86
+ res.status(400).json({ error: { message: 'Expected non-empty body', statusCode: 400, code: '400' } })
87
+ return
88
+ }
89
+ }
90
+
91
+ return jsonBodyParser(req, res, next)
92
+ })
93
+ // batch
94
+ .post('/\\$batch', require('./middleware/batch')(this))
95
+ // handle
96
+ // REVISIT: with old adapter, we return 405 for HEAD requests -> check OData spec
97
+ .head('*', (_, res) => res.sendStatus(405))
98
+ .post('*', operation4(this), create4(this))
99
+ .get('*', operation4(this), stream4(this), read4(this))
100
+ .put('*', update4(this), create4(this, 'upsert'))
101
+ .patch('*', update4(this), create4(this, 'upsert'))
102
+ .delete('*', delete4(this))
103
+ // error
104
+ .use(error4(this))
105
+ )
106
+ }
107
+
108
+ request4(args) {
109
+ return new NoaRequest(args)
110
+ }
111
+ }
112
+
113
+ // REVISIT: ugly hack -> eliminate
114
+ class NoaRequest extends cds.Request {
115
+ // REVISIT: all usages of .protocol are very bad style, violating modularization
116
+ get protocol() {
117
+ return 'odata'
118
+ }
119
+ // AFC uses unofficial req._queryOptions -> which is bad! -> should eliminate
120
+ get _queryOptions() {
121
+ cds.utils.deprecated({ kind: '', old: 'req._queryOptions', new: 'req._.req.query' })
122
+ return this.req?.query
123
+ }
124
+ }
125
+
126
+ module.exports = Object.assign(ODataAdapter, { NoaRequest })
@@ -78,7 +78,12 @@ const _2query = cqn => {
78
78
  }
79
79
 
80
80
  const enhanceCqn = (cqn, options) => {
81
- if (options.afterburner) cqn = options.afterburner(cqn)
81
+ if (options.afterburner) {
82
+ const { service, protocol } = options
83
+ let { model, definition: { name: namespace } } = service // prettier-ignore
84
+ if (service.isExtensible) model = cds.context?.model || model
85
+ cqn = options.afterburner(cqn, model, namespace, protocol)
86
+ }
82
87
 
83
88
  const query = _2query(cqn)
84
89
 
@@ -101,8 +106,16 @@ module.exports = {
101
106
 
102
107
  url = decodeURIComponent(url)
103
108
 
109
+ // REVISIT: compat for bad url in mtxs tests (cf. #957)
110
+ if (url.match(/\?\?/)) {
111
+ const split = url.split('?')
112
+ url = split.shift() + '?'
113
+ while (split[0] === '') split.shift()
114
+ url += split.join('?')
115
+ }
116
+
104
117
  options = options === 'strict' ? { strict } : options.strict ? { ...options, strict } : options
105
- if (options.service) Object.assign(options, { minimal: true, afterburner: afterburner.for(options.service) })
118
+ if (options.service?.model) Object.assign(options, { minimal: true, afterburner })
106
119
  options.safeNumber = safeNumber
107
120
  options.skipToken = require('./utils').skipToken
108
121