@sap/cds 7.9.4 → 8.0.4

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 (276) hide show
  1. package/CHANGELOG.md +128 -3659
  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 +30 -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/tree.js +0 -1
  116. package/libx/_runtime/common/composition/update.js +3 -3
  117. package/libx/_runtime/common/error/frontend.js +21 -12
  118. package/libx/_runtime/common/error/log.js +36 -0
  119. package/libx/_runtime/common/error/utils.js +2 -5
  120. package/libx/_runtime/common/generic/auth/autoexpose.js +18 -17
  121. package/libx/_runtime/common/generic/auth/expand.js +1 -1
  122. package/libx/_runtime/common/generic/auth/readOnly.js +1 -2
  123. package/libx/_runtime/common/generic/auth/restrict.js +23 -42
  124. package/libx/_runtime/common/generic/auth/restrictions.js +2 -7
  125. package/libx/_runtime/common/generic/auth/utils.js +91 -88
  126. package/libx/_runtime/common/generic/crud.js +6 -5
  127. package/libx/_runtime/common/generic/etag.js +7 -12
  128. package/libx/_runtime/common/generic/input.js +70 -68
  129. package/libx/_runtime/common/generic/paging.js +1 -0
  130. package/libx/_runtime/common/generic/sorting.js +1 -0
  131. package/libx/_runtime/common/generic/temporal.js +8 -2
  132. package/libx/_runtime/common/i18n/index.js +1 -1
  133. package/libx/_runtime/common/i18n/messages.properties +3 -1
  134. package/libx/_runtime/common/utils/binary.js +8 -2
  135. package/libx/_runtime/common/utils/compareJson.js +5 -1
  136. package/libx/_runtime/common/utils/copy.js +6 -11
  137. package/libx/_runtime/common/utils/cqn2cqn4sql.js +16 -14
  138. package/libx/_runtime/common/utils/differ.js +3 -6
  139. package/libx/_runtime/common/utils/keys.js +77 -18
  140. package/libx/_runtime/common/utils/postProcess.js +12 -15
  141. package/libx/_runtime/common/utils/propagateForeignKeys.js +0 -1
  142. package/libx/_runtime/common/utils/resolveView.js +2 -3
  143. package/libx/_runtime/common/utils/restrictions.js +45 -17
  144. package/libx/_runtime/common/utils/rewriteAsterisks.js +1 -8
  145. package/libx/_runtime/common/utils/stream.js +3 -16
  146. package/libx/_runtime/common/utils/streamProp.js +8 -18
  147. package/libx/_runtime/common/utils/structured.js +1 -1
  148. package/libx/_runtime/common/utils/ucsn.js +0 -2
  149. package/libx/_runtime/db/Service.js +0 -72
  150. package/libx/_runtime/db/data-conversion/post-processing.js +0 -1
  151. package/libx/_runtime/db/expand/expandCQNToJoin.js +9 -9
  152. package/libx/_runtime/db/expand/rawToExpanded.js +0 -8
  153. package/libx/_runtime/db/generic/input.js +3 -8
  154. package/libx/_runtime/db/generic/rewrite.js +1 -0
  155. package/libx/_runtime/db/query/read.js +2 -2
  156. package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +0 -1
  157. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  158. package/libx/_runtime/db/utils/columns.js +2 -6
  159. package/libx/_runtime/fiori/lean-draft.js +138 -56
  160. package/libx/_runtime/hana/Service.js +0 -1
  161. package/libx/_runtime/hana/driver.js +1 -1
  162. package/libx/_runtime/hana/dynatrace.js +1 -2
  163. package/libx/_runtime/hana/pool.js +11 -21
  164. package/libx/_runtime/hana/streaming.js +0 -1
  165. package/libx/_runtime/messaging/common-utils/AMQPClient.js +0 -1
  166. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  167. package/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
  168. package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +1 -1
  169. package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +19 -33
  170. package/libx/_runtime/messaging/event-broker.js +54 -27
  171. package/libx/_runtime/messaging/file-based.js +3 -3
  172. package/libx/_runtime/messaging/http-utils/token.js +1 -1
  173. package/libx/_runtime/messaging/kafka.js +2 -2
  174. package/libx/_runtime/messaging/redis-messaging.js +0 -1
  175. package/libx/_runtime/remote/Service.js +25 -25
  176. package/libx/_runtime/remote/utils/client.js +4 -5
  177. package/libx/_runtime/remote/utils/cloudSdkProvider.js +0 -3
  178. package/libx/_runtime/remote/utils/data.js +0 -1
  179. package/libx/_runtime/sqlite/Service.js +1 -2
  180. package/libx/_runtime/ucl/Service.js +37 -78
  181. package/libx/common/assert/index.js +22 -21
  182. package/libx/common/assert/type-relaxed.js +39 -0
  183. package/libx/common/assert/utils.js +3 -2
  184. package/libx/common/assert/validation.js +3 -8
  185. package/libx/common/utils/index.js +5 -0
  186. package/libx/common/utils/path.js +51 -0
  187. package/libx/odata/ODataAdapter.js +126 -0
  188. package/libx/odata/index.js +15 -2
  189. package/libx/odata/middleware/batch.js +320 -84
  190. package/libx/odata/middleware/body-parser.js +33 -0
  191. package/libx/odata/middleware/create.js +44 -59
  192. package/libx/odata/middleware/delete.js +23 -12
  193. package/libx/odata/middleware/error.js +30 -6
  194. package/libx/odata/middleware/metadata.js +38 -26
  195. package/libx/odata/middleware/operation.js +93 -69
  196. package/libx/odata/middleware/parse.js +6 -8
  197. package/libx/odata/middleware/read.js +117 -93
  198. package/libx/odata/middleware/service-document.js +22 -19
  199. package/libx/odata/middleware/stream.js +54 -56
  200. package/libx/odata/middleware/update.js +79 -87
  201. package/libx/odata/parse/afterburner.js +191 -175
  202. package/libx/odata/parse/cqn2odata.js +5 -5
  203. package/libx/odata/parse/grammar.peggy +27 -20
  204. package/libx/odata/parse/multipartToJson.js +17 -9
  205. package/libx/odata/parse/parser.js +1 -1
  206. package/libx/odata/utils/etag.js +14 -6
  207. package/libx/odata/utils/index.js +84 -12
  208. package/libx/odata/utils/metadata.js +161 -0
  209. package/libx/odata/utils/postProcess.js +89 -0
  210. package/libx/odata/utils/readAfterWrite.js +134 -17
  211. package/libx/odata/utils/result.js +36 -142
  212. package/libx/outbox/index.js +4 -3
  213. package/libx/rest/RestAdapter.js +115 -182
  214. package/libx/rest/middleware/create.js +28 -24
  215. package/libx/rest/middleware/delete.js +7 -10
  216. package/libx/rest/middleware/error.js +26 -16
  217. package/libx/rest/middleware/operation.js +48 -41
  218. package/libx/rest/middleware/parse.js +128 -126
  219. package/libx/rest/middleware/read.js +20 -27
  220. package/libx/rest/middleware/update.js +26 -31
  221. package/package.json +17 -8
  222. package/server.js +4 -2
  223. package/apis/cds.d.ts +0 -3
  224. package/apis/core.d.ts +0 -21
  225. package/apis/cqn.d.ts +0 -18
  226. package/apis/csn.d.ts +0 -21
  227. package/apis/events.d.ts +0 -18
  228. package/apis/internal/inference.d.ts +0 -18
  229. package/apis/linked.d.ts +0 -18
  230. package/apis/log.d.ts +0 -20
  231. package/apis/models.d.ts +0 -18
  232. package/apis/ql.d.ts +0 -18
  233. package/apis/reflect.d.ts +0 -32
  234. package/apis/server.d.ts +0 -18
  235. package/apis/services.d.ts +0 -22
  236. package/bin/cds-serve.js +0 -56
  237. package/lib/compile/to/gql.js +0 -15
  238. package/lib/srv/protocols/_legacy.js +0 -44
  239. package/lib/utils/jest.js +0 -43
  240. package/libx/_runtime/auth/index.js +0 -193
  241. package/libx/_runtime/auth/strategies/JWT.js +0 -37
  242. package/libx/_runtime/auth/strategies/basic.js +0 -20
  243. package/libx/_runtime/auth/strategies/dummy.js +0 -14
  244. package/libx/_runtime/auth/strategies/ias-auth.js +0 -1
  245. package/libx/_runtime/auth/strategies/mock.js +0 -77
  246. package/libx/_runtime/auth/strategies/xssecUtils.js +0 -93
  247. package/libx/_runtime/auth/strategies/xsuaa.js +0 -38
  248. package/libx/_runtime/common/perf/index.js +0 -19
  249. package/libx/_runtime/common/utils/ensureIEEE754.js +0 -29
  250. package/libx/_runtime/fiori/draft.js +0 -2
  251. package/libx/_runtime/fiori/generic/activate.js +0 -190
  252. package/libx/_runtime/fiori/generic/before.js +0 -201
  253. package/libx/_runtime/fiori/generic/cancel.js +0 -19
  254. package/libx/_runtime/fiori/generic/delete.js +0 -21
  255. package/libx/_runtime/fiori/generic/edit.js +0 -157
  256. package/libx/_runtime/fiori/generic/index.js +0 -25
  257. package/libx/_runtime/fiori/generic/new.js +0 -82
  258. package/libx/_runtime/fiori/generic/patch.js +0 -101
  259. package/libx/_runtime/fiori/generic/prepare.js +0 -57
  260. package/libx/_runtime/fiori/generic/read.js +0 -1340
  261. package/libx/_runtime/fiori/generic/readOverDraft.js +0 -146
  262. package/libx/_runtime/fiori/utils/csn.js +0 -13
  263. package/libx/_runtime/fiori/utils/delete.js +0 -114
  264. package/libx/_runtime/fiori/utils/handler.js +0 -264
  265. package/libx/_runtime/fiori/utils/lockInfo.js +0 -27
  266. package/libx/_runtime/fiori/utils/req.js +0 -23
  267. package/libx/_runtime/fiori/utils/stream.js +0 -36
  268. package/libx/_runtime/fiori/utils/where.js +0 -254
  269. package/libx/_runtime/index.js +0 -22
  270. package/libx/odata/utils/handler.js +0 -120
  271. package/libx/odata/utils/metaInfo.js +0 -410
  272. package/libx/odata/utils/path.js +0 -75
  273. package/libx/rest/RestRequest.js +0 -32
  274. package/libx/rest/index.js +0 -3
  275. package/libx/rest/readme.md +0 -1
  276. /package/libx/common/assert/{type.js → type-strict.js} +0 -0
@@ -1,65 +1,48 @@
1
1
  const cds = require('../cds')
2
2
  const LOG = cds.log('ucl')
3
+ const fs = require('fs').promises
3
4
 
4
5
  const https = require('https')
5
6
 
6
7
  class UCLService extends cds.Service {
7
8
  async init() {
8
9
  await super.init()
9
- this.validate()
10
- this._register()
11
- this.agent = this.getAgent()
12
- }
13
10
 
14
- getAgent() {
15
- try {
16
- if (this.options.x509.certPath && this.options.x509.pkeyPath) {
17
- return new https.Agent({
18
- cert: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.certPath)),
19
- key: cds.utils.fs.readFileSync(cds.utils.path.resolve(cds.root, this.options.x509.pkeyPath))
20
- })
21
- }
22
- } catch (error) {
23
- if (LOG) LOG.error('GetCredentials', { error: error.message })
24
- throw error
11
+ for (const _required of ['namespace', 'systemType', 'systemDescription']) {
12
+ if (!this.options[_required])
13
+ throw new Error(
14
+ `The UCL service requires mandatory parameter \`${_required}\`, please provide it as described in the documentation.`
15
+ )
25
16
  }
26
- }
27
17
 
28
- async _registerProvisioningEvents() {
29
- var provisioning
30
- try {
31
- provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
32
- } catch (error) {
18
+ if (!cds.requires.multitenancy && cds.env.profile !== 'mtx-sidecar')
33
19
  throw new Error(
34
- "Provisioning service 'cds.xt.SaasProvisioningService' can not be found, therefore mode is not multitenant. Single tenant applications are not supported."
20
+ 'The UCL service requires multitenancy, please enable it in your cds configuration with `cds.requires.multitenancy` or by using the mtx sidecar.'
35
21
  )
36
- }
37
- if (provisioning) {
22
+ if (!this.options.credentials)
23
+ throw new Error('No credentials found for the UCL service, please bind the service to your app.')
24
+
25
+ if (!this.options.x509.certPath || !this.options.x509.pkeyPath)
26
+ throw new Error('The UCL service requires the options `x509.certPath` and `x509.pkeyPath`.')
27
+ const [cert, key] = await Promise.all([
28
+ fs.readFile(cds.utils.path.resolve(cds.root, this.options.x509.certPath)),
29
+ fs.readFile(cds.utils.path.resolve(cds.root, this.options.x509.pkeyPath))
30
+ ])
31
+ this.agent = new https.Agent({ cert, key })
32
+
33
+ const existingTemplate = await this.readTemplate()
34
+ const template = existingTemplate ? await this.updateTemplate(existingTemplate) : await this.createTemplate() // TODO: Make sure return value is correct
35
+
36
+ cds.once('listening', async () => {
37
+ const provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
38
38
  provisioning.prepend(() => {
39
39
  provisioning.on('dependencies', async (_, next) => {
40
- let dependencies = await next()
41
- const xsappnameCMPClone = await this._getUCLDependency()
42
- dependencies.push({ xsappname: xsappnameCMPClone })
40
+ const dependencies = await next()
41
+ dependencies.push({ xsappname: template.labels.xsappnameCMPClone })
43
42
  return dependencies
44
43
  })
45
44
  })
46
- }
47
- }
48
-
49
- validate() {
50
- if (!this.options.namespace) {
51
- throw new Error(
52
- 'UCL integrator requires an application namespace. You can set environment variable SAP_APPLICATION_NAMESPACE or you can give namespace as an option in your cds.requires section as described in documentation'
53
- )
54
- }
55
- if (!cds.requires.multitenancy && cds.env.profile !== 'mtx-sidecar') {
56
- throw new Error('[ucl] - Currently only multitenant applications are supported.')
57
- }
58
- if (!this.options.systemType || !this.options.systemDescription) {
59
- throw new Error(
60
- 'systemType and systemDescription is obligatory parameters, please fill as shown in documentation'
61
- )
62
- }
45
+ })
63
46
  }
64
47
 
65
48
  async readTemplate() {
@@ -85,10 +68,10 @@ class UCLService extends cds.Service {
85
68
  }
86
69
  `
87
70
  const variables = { key: 'xsappname', value: `"${xsappname}"` }
88
- return (await this.request(query, variables)).applicationTemplates.data[0]
71
+ return (await this._request(query, variables)).applicationTemplates.data[0]
89
72
  }
90
73
 
91
- async _createTemplate() {
74
+ async createTemplate() {
92
75
  const xsappname = this.options.credentials.xsappname
93
76
  const query = `mutation {
94
77
  result: createApplicationTemplate (
@@ -124,13 +107,13 @@ class UCLService extends cds.Service {
124
107
  }
125
108
  }`
126
109
  try {
127
- return this.handleResponse(await this.request(query))
110
+ return this._handleResponse(await this._request(query))
128
111
  } catch (e) {
129
- this.handleResponse(e)
112
+ this._handleResponse(e)
130
113
  }
131
114
  }
132
115
 
133
- handleResponse(result) {
116
+ _handleResponse(result) {
134
117
  if (result.response && result.response.errors) {
135
118
  let errorMessage = result.response.errors[0].message
136
119
  throw new Error(errorMessage)
@@ -151,18 +134,11 @@ class UCLService extends cds.Service {
151
134
  description
152
135
  }
153
136
  }`
154
- return this.handleResponse(await this.request(query))
155
- }
156
-
157
- async _getUCLDependency() {
158
- if (!this.template) {
159
- throw Error('Application template not found on UCL!')
160
- }
161
- return this.template.labels.xsappnameCMPClone
137
+ return this._handleResponse(await this._request(query))
162
138
  }
163
139
 
164
140
  // Replace with fetch
165
- async request(query, variables) {
141
+ async _request(query, variables) {
166
142
  const opts = {
167
143
  host: this.options.host,
168
144
  path: this.options.path,
@@ -201,17 +177,7 @@ class UCLService extends cds.Service {
201
177
  })
202
178
  }
203
179
 
204
- async _registerApplicationTemplate() {
205
- this.template = await this.readTemplate()
206
- if (!this.template) {
207
- LOG.info('Application Template cannot be found therefore created.')
208
- await this._createTemplate()
209
- } else {
210
- await this._updateTemplate(this.template)
211
- }
212
- }
213
-
214
- async _updateTemplate(template) {
180
+ async updateTemplate(template) {
215
181
  const query = `mutation {
216
182
  result: updateApplicationTemplate(
217
183
  id: "${template.id}"
@@ -240,20 +206,13 @@ class UCLService extends cds.Service {
240
206
  }
241
207
  }`
242
208
  try {
243
- const response = this.handleResponse(await this.request(query))
209
+ const response = this._handleResponse(await this._request(query))
244
210
  LOG.info('Application template updated successfully.')
245
211
  return response
246
212
  } catch (e) {
247
- this.handleResponse(e)
213
+ this._handleResponse(e)
248
214
  }
249
215
  }
250
-
251
- _register() {
252
- cds.once('listening', async () => {
253
- await this._registerApplicationTemplate()
254
- this._registerProvisioningEvents()
255
- })
256
- }
257
216
  }
258
217
 
259
218
  module.exports = UCLService
@@ -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