@sap/cds-dk 7.9.6 → 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 (291) hide show
  1. package/CHANGELOG.md +2136 -0
  2. package/bin/add.js +18 -52
  3. package/bin/bind.js +2 -2
  4. package/bin/build.js +29 -4
  5. package/bin/cds.js +31 -14
  6. package/bin/compile/index.js +7 -6
  7. package/bin/completion/index.js +70 -46
  8. package/bin/completion/scripts/cds-bash.sh +4 -1
  9. package/bin/completion/scripts/cds-fish.fish +13 -0
  10. package/bin/completion/scripts/cds-ps.ps1 +2 -1
  11. package/bin/completion/scripts/cds-zsh.sh +2 -1
  12. package/bin/deploy/index.js +6 -5
  13. package/bin/deploy/to-sqlite.js +2 -2
  14. package/bin/extend.js +19 -50
  15. package/bin/help.js +2 -2
  16. package/bin/init.js +10 -77
  17. package/bin/login.js +13 -5
  18. package/bin/migrate.js +1 -1
  19. package/bin/pull.js +11 -0
  20. package/bin/repl.js +3 -0
  21. package/bin/test.js +5 -0
  22. package/bin/version.js +7 -7
  23. package/bin/watch.js +13 -14
  24. package/lib/bind/KubernetesApiAccessor.js +10 -9
  25. package/lib/build/buildTaskEngine.js +11 -7
  26. package/lib/build/buildTaskFactory.js +15 -43
  27. package/lib/build/buildTaskProviderFactory.js +16 -44
  28. package/lib/build/constants.js +4 -3
  29. package/lib/build/index.js +2 -2
  30. package/lib/build/{buildPlugin.js → plugin.js} +65 -46
  31. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/Chart.yaml +1 -1
  32. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/values.yaml +1 -1
  33. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/service-instance/Chart.yaml +1 -1
  34. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/service-instance/templates/_helpers.tpl +7 -0
  35. package/lib/build/plugins/helm/files/subcharts/service-instance/templates/servicebinding.yaml +32 -0
  36. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/service-instance/templates/serviceinstance.yaml +3 -0
  37. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/service-instance/values.schema.json +63 -0
  38. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/service-instance/values.yaml +23 -1
  39. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/Chart.yaml +1 -1
  40. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/deployment.yaml +2 -0
  41. package/lib/build/plugins/helm/files/subcharts/web-application/templates/hpa.yaml +22 -0
  42. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/pod-disruption-budget.yaml +1 -0
  43. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/values.schema.json +49 -0
  44. package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/values.yaml +8 -2
  45. package/lib/{init/template/helm/files/chart → build/plugins/helm/files}/templates/_deployment_helpers.tpl +5 -0
  46. package/lib/build/plugins/helm/files/templates/_helpers.tpl +13 -0
  47. package/lib/build/plugins/helm/index.js +81 -0
  48. package/lib/build/plugins.js +12 -15
  49. package/lib/build/provider/featureToggleBuildPlugin.js +7 -39
  50. package/lib/build/provider/hana/2migration.js +168 -124
  51. package/lib/build/provider/hana/index.js +69 -56
  52. package/lib/build/provider/hana/template/package.json +4 -3
  53. package/lib/build/provider/internalBuildPlugin.js +32 -97
  54. package/lib/build/provider/internalBuildTaskProvider.js +12 -48
  55. package/lib/build/provider/java/index.js +2 -3
  56. package/lib/build/provider/mtx/index.js +5 -4
  57. package/lib/build/provider/mtx-extension/index.js +5 -2
  58. package/lib/build/provider/mtx-sidecar/index.js +2 -2
  59. package/lib/build/provider/nodejs/index.js +4 -3
  60. package/lib/build/util.js +83 -12
  61. package/lib/compile/to-mermaid.js +108 -0
  62. package/lib/compile/to_asyncapi.js +7 -0
  63. package/lib/compile/to_openapi.js +7 -0
  64. package/lib/deploy/to-hana/hana.js +37 -34
  65. package/lib/deploy/to-hana/hdiDeployUtil.js +8 -6
  66. package/lib/env/schemas/cds-rc.js +1 -1
  67. package/lib/import/common.js +2 -2
  68. package/lib/import/odata/metadataconverter/MetadataConverter.js +4 -4
  69. package/lib/import/odata/v2parser.js +83 -197
  70. package/lib/import/odata/v4parser.js +1 -38
  71. package/lib/import/openapi/importOpenAPI.js +164 -42
  72. package/lib/index.js +10 -12
  73. package/lib/init/bas.js +91 -0
  74. package/lib/init/constants.js +21 -11
  75. package/lib/init/index.js +72 -150
  76. package/lib/init/merge.js +12 -20
  77. package/lib/init/mvn.js +103 -150
  78. package/lib/init/plugin.js +11 -7
  79. package/lib/init/projectReader.js +53 -20
  80. package/lib/init/registries/mta.js +51 -6
  81. package/lib/init/template/application-logging/files/package.json.hbs +7 -0
  82. package/lib/init/template/application-logging/index.js +13 -1
  83. package/lib/init/template/approuter/files/containerize.yaml.hbs +10 -0
  84. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -2
  85. package/lib/init/template/approuter/files/values.yaml.hbs +19 -4
  86. package/lib/init/template/approuter/files/xs-app.json.hbs +12 -3
  87. package/lib/init/template/approuter/index.js +29 -25
  88. package/lib/init/template/attachments/files/mta.yaml.hbs +14 -0
  89. package/lib/init/template/attachments/files/package.json.hbs +5 -0
  90. package/lib/init/template/attachments/files/values.yaml +7 -0
  91. package/lib/init/template/attachments/index.js +53 -0
  92. package/lib/init/template/{redis-messaging → audit-logging}/files/mta.yaml.hbs +4 -4
  93. package/lib/init/template/audit-logging/files/package.json.hbs +7 -0
  94. package/lib/init/template/audit-logging/index.js +66 -0
  95. package/lib/init/template/cf-manifest/index.js +4 -0
  96. package/lib/init/template/completion/completionSetup.js +54 -33
  97. package/lib/init/template/completion/index.js +7 -1
  98. package/lib/init/template/connectivity/index.js +4 -4
  99. package/lib/init/template/containerize/files/containerize.yaml.hbs +26 -0
  100. package/lib/init/template/containerize/index.js +24 -0
  101. package/lib/init/template/data/index.js +4 -0
  102. package/lib/init/template/destination/files/mta.yaml.hbs +31 -1
  103. package/lib/init/template/destination/files/package.sidecar.json +7 -0
  104. package/lib/init/template/destination/index.js +22 -2
  105. package/lib/init/template/enterprise-messaging/index.js +6 -3
  106. package/lib/init/template/enterprise-messaging-shared/index.js +5 -1
  107. package/lib/init/template/extensibility/files/package.json.hbs +1 -1
  108. package/lib/init/template/extensibility/files/xs-security.json.hbs +2 -8
  109. package/lib/init/template/extensibility/index.js +3 -1
  110. package/lib/init/template/file-based-messaging/index.js +4 -0
  111. package/lib/init/template/h2/index.js +4 -0
  112. package/lib/init/template/hana/files/containerize.yaml.hbs +10 -0
  113. package/lib/init/template/hana/files/db/package.json +3 -2
  114. package/lib/init/template/hana/files/package.json.hbs +4 -7
  115. package/lib/init/template/hana/files/values.yaml.hbs +1 -2
  116. package/lib/init/template/hana/index.js +15 -7
  117. package/lib/init/template/helm/files/Chart.yaml.hbs +2 -3
  118. package/lib/init/template/helm/files/values.schema.json +2173 -0
  119. package/lib/init/template/helm/files/values.yaml.hbs +11 -6
  120. package/lib/init/template/helm/index.js +77 -18
  121. package/lib/init/template/helm-unified-runtime/index.js +19 -56
  122. package/lib/init/template/html5-repo/files/app-package.json.hbs +11 -7
  123. package/lib/init/template/html5-repo/files/manifest.json +13 -0
  124. package/lib/init/template/html5-repo/files/mta.yaml.hbs +14 -33
  125. package/lib/init/template/html5-repo/files/package.sidecar.json +7 -0
  126. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +21 -0
  127. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +2 -29
  128. package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -2
  129. package/lib/init/template/html5-repo/files/xs-app.json.hbs +6 -7
  130. package/lib/init/template/html5-repo/index.js +41 -35
  131. package/lib/init/template/http/index.js +36 -12
  132. package/lib/init/template/java/index.js +18 -12
  133. package/lib/init/template/kafka/index.js +4 -0
  134. package/lib/init/template/lint/index.js +7 -2
  135. package/lib/init/template/liquibase/index.js +4 -0
  136. package/lib/init/template/local-messaging/index.js +4 -0
  137. package/lib/init/template/malware-scanner/files/mta.yaml.hbs +14 -0
  138. package/lib/init/template/malware-scanner/files/package.json.hbs +17 -0
  139. package/lib/init/template/malware-scanner/files/values.yaml +7 -0
  140. package/lib/init/template/malware-scanner/index.js +47 -0
  141. package/lib/init/template/mta/files/mta.yaml.hbs +3 -3
  142. package/lib/init/template/mta/files/package.json +1 -1
  143. package/lib/init/template/mta/index.js +4 -0
  144. package/lib/init/template/mtx/index.js +4 -0
  145. package/lib/init/template/multitenancy/files/containerize.yaml.hbs +10 -0
  146. package/lib/init/template/multitenancy/files/mta.yaml.hbs +0 -1
  147. package/lib/init/template/multitenancy/files/package.json.hbs +1 -1
  148. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +6 -5
  149. package/lib/init/template/multitenancy/files/values.yaml.hbs +14 -24
  150. package/lib/init/template/multitenancy/index.js +50 -28
  151. package/lib/init/template/nodejs/files/eslint.config.mjs +2 -0
  152. package/lib/init/template/nodejs/files/package.json.hbs +2 -3
  153. package/lib/init/template/nodejs/index.js +4 -0
  154. package/lib/init/template/notifications/files/mta.yaml.hbs +2 -2
  155. package/lib/init/template/notifications/index.js +4 -0
  156. package/lib/init/template/pipeline/files/.pipeline/config.yml +1 -1
  157. package/lib/init/template/pipeline/files/Jenkinsfile +1 -1
  158. package/lib/init/template/pipeline/index.js +4 -0
  159. package/lib/init/template/portal/files/mta.yaml.hbs +52 -0
  160. package/lib/init/template/portal/files/package.json.hbs +11 -0
  161. package/lib/init/template/portal/files/package.sidecar.json +7 -0
  162. package/lib/init/template/portal/files/portal-site/CommonDataModel.json.hbs +97 -0
  163. package/lib/init/template/portal/files/portal-site/i18n/catalog.properties +1 -0
  164. package/lib/init/template/portal/files/portal-site/i18n/catalog_de.properties +1 -0
  165. package/lib/init/template/portal/files/portal-site/i18n/group.properties +1 -0
  166. package/lib/init/template/portal/files/portal-site/i18n/group_de.properties +1 -0
  167. package/lib/init/template/portal/index.js +84 -0
  168. package/lib/init/template/postgres/index.js +5 -1
  169. package/lib/init/template/redis/files/mta.yaml.hbs +25 -0
  170. package/lib/init/template/redis/files/package.json.hbs +5 -0
  171. package/lib/init/template/redis/index.js +44 -0
  172. package/lib/init/template/redis-messaging/files/package.json.hbs +0 -3
  173. package/lib/init/template/redis-messaging/index.js +8 -18
  174. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +3 -0
  175. package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +3 -0
  176. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +13 -1
  177. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +2 -2
  178. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +3 -0
  179. package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +4 -0
  180. package/lib/init/template/sample/files/app/browse/webapp/manifest.json +3 -3
  181. package/lib/init/template/sample/files/app/index.html +1 -1
  182. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Authors.csv +5 -5
  183. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +6 -6
  184. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books_texts.csv +5 -5
  185. package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +16 -16
  186. package/lib/init/template/sample/files/nodejs/cat-service.js +2 -2
  187. package/lib/init/template/sample/files/package.json.hbs +3 -0
  188. package/lib/init/template/sample/index.js +16 -9
  189. package/lib/init/template/sqlite/index.js +4 -0
  190. package/lib/init/template/tiny-sample/files/nodejs/db/data/my.bookshop-Books.csv +3 -3
  191. package/lib/init/template/tiny-sample/files/nodejs/srv/cat-service.cds.hbs +1 -1
  192. package/lib/init/template/tiny-sample/index.js +4 -0
  193. package/lib/init/template/toggles/files/package.json.hbs +1 -1
  194. package/lib/init/template/toggles/index.js +4 -0
  195. package/lib/init/template/typer/index.js +4 -0
  196. package/lib/init/template/typescript/files/package.json +12 -0
  197. package/lib/init/template/typescript/files/tsconfig.json +13 -0
  198. package/lib/init/template/typescript/index.js +39 -0
  199. package/lib/init/template/workzone/index.js +9 -0
  200. package/lib/init/template/workzone-standard/files/manifest.json.hbs +6 -0
  201. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +59 -0
  202. package/lib/init/template/workzone-standard/index.js +46 -0
  203. package/lib/init/template/xsuaa/files/package.json.hbs +1 -1
  204. package/lib/init/template/xsuaa/files/values.yaml.hbs +3 -2
  205. package/lib/init/template/xsuaa/index.js +11 -7
  206. package/lib/init/validate.js +27 -0
  207. package/lib/lint/generators.js +1 -1
  208. package/lib/lint/io.js +25 -7
  209. package/lib/mtx/auth_manager.js +64 -30
  210. package/lib/mtx/download_legacy.js +2 -2
  211. package/lib/mtx/index.js +0 -4
  212. package/lib/mtx/params.js +32 -38
  213. package/lib/mtx/pull.js +151 -43
  214. package/lib/mtx/push.js +3 -3
  215. package/lib/mtx/settings_manager.js +75 -35
  216. package/lib/mtx/tenant_action.js +1 -1
  217. package/lib/mtx/util/cf.js +1 -1
  218. package/lib/mtx/util/fs.js +26 -1
  219. package/lib/mtx/util/logging.js +7 -3
  220. package/lib/util/command.js +0 -2
  221. package/lib/util/fs.js +2 -3
  222. package/lib/util/question.js +28 -0
  223. package/lib/util/term.js +5 -3
  224. package/npm-shrinkwrap.json +225 -1083
  225. package/package.json +13 -8
  226. package/bin/activate.js +0 -77
  227. package/lib/compile/asyncapi/channels.js +0 -22
  228. package/lib/compile/asyncapi/components/messageTraits/index.js +0 -89
  229. package/lib/compile/asyncapi/components/messages/index.js +0 -173
  230. package/lib/compile/asyncapi/components/schemas/annotations.js +0 -33
  231. package/lib/compile/asyncapi/components/schemas/array.js +0 -8
  232. package/lib/compile/asyncapi/components/schemas/cache.js +0 -14
  233. package/lib/compile/asyncapi/components/schemas/definition.js +0 -187
  234. package/lib/compile/asyncapi/components/schemas/enum.js +0 -16
  235. package/lib/compile/asyncapi/components/schemas/examples.js +0 -10
  236. package/lib/compile/asyncapi/components/schemas/index.js +0 -14
  237. package/lib/compile/asyncapi/components/schemas/localized.js +0 -29
  238. package/lib/compile/asyncapi/components/schemas/primitiveElement.js +0 -41
  239. package/lib/compile/asyncapi/components/schemas/ref.js +0 -45
  240. package/lib/compile/asyncapi/components/schemas/utils.js +0 -108
  241. package/lib/compile/asyncapi/index.js +0 -247
  242. package/lib/compile/asyncapi/message.js +0 -13
  243. package/lib/compile/asyncapi/utils.js +0 -27
  244. package/lib/compile/openapi/csdl2openapi.js +0 -2448
  245. package/lib/compile/openapi/index.js +0 -68
  246. package/lib/init/template/helm/files/chart/templates/NOTES.txt +0 -14
  247. package/lib/init/template/helm/files/chart/templates/_helpers.tpl +0 -28
  248. package/lib/init/template/helm/files/chart/values.root.schema.json +0 -61
  249. package/lib/init/template/helm/files/chart/values.schema.json +0 -366
  250. package/lib/init/template/helm-unified-runtime/files/Chart.yaml.hbs +0 -10
  251. package/lib/init/template/helm-unified-runtime/files/templates/_deployment_helpers.tpl +0 -90
  252. package/lib/init/template/helm-unified-runtime/files/templates/_helpers.tpl +0 -28
  253. package/lib/init/template/helm-unified-runtime/files/values.root.schema.json +0 -61
  254. package/lib/init/template/helm-unified-runtime/files/values.schema.json +0 -366
  255. package/lib/init/template/helm-unified-runtime/files/values.yaml.hbs +0 -26
  256. package/lib/init/template/helm-unified-runtime/package.json +0 -4
  257. package/lib/init/template/multitenancy/files/mtxs-configmap-java-approuter.yaml +0 -20
  258. package/lib/init/template/multitenancy/files/mtxs-configmap-java.yaml +0 -18
  259. package/lib/init/template/multitenancy/files/mtxs-configmap-nodejs-approuter.yaml +0 -12
  260. package/lib/init/template/multitenancy/files/mtxs-configmap-nodejs.yaml +0 -12
  261. package/lib/init/template/multitenancy/files/saas-registry-secret.yaml +0 -9
  262. package/lib/init/template/nodejs/files/eslint.config.js +0 -13
  263. package/lib/init/template/notebook/index.js +0 -11
  264. package/lib/init/template/samples/index.js +0 -9
  265. package/lib/mtx/activate.js +0 -238
  266. package/lib/mtx/extend.js +0 -173
  267. package/lib/util/exec.d.ts +0 -54
  268. package/lib/util/exec.js +0 -211
  269. /package/lib/{init/template/approuter/files → build/plugins/helm/files/plugins/approuter}/approuter-configmap.yaml +0 -0
  270. /package/lib/{init/template/connectivity/files → build/plugins/helm/files/plugins/connectivity}/connectivity-proxy-info.yaml +0 -0
  271. /package/lib/{init/template/html5-repo/files → build/plugins/helm/files/plugins/html5-repo}/html5-apps-deployer-configmap.yaml +0 -0
  272. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/templates/_helpers.tpl +0 -0
  273. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/templates/job.yaml +0 -0
  274. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/templates/secret.yaml +0 -0
  275. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/templates/service-account.yaml +0 -0
  276. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/templates/service-binding.yaml +0 -0
  277. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/content-deployment/values.schema.json +0 -0
  278. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/NOTES.txt +0 -0
  279. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/_api.tpl +0 -0
  280. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/_helpers.tpl +0 -0
  281. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/api-rule.yaml +0 -0
  282. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/network-policy.yaml +0 -0
  283. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/rbac.yaml +0 -0
  284. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/secret.yaml +0 -0
  285. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/service-account.yaml +0 -0
  286. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/service-binding.yaml +0 -0
  287. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/service.yaml +0 -0
  288. /package/lib/{init/template/helm/files → build/plugins/helm/files/subcharts}/web-application/templates/virtual-service.yaml +0 -0
  289. /package/lib/{init/template/helm-unified-runtime → build/plugins/helm}/files/templates/NOTES.txt +0 -0
  290. /package/lib/init/template/{nodejs/files → java}/.cdsrc.json +0 -0
  291. /package/lib/init/template/tiny-sample/files/nodejs/db/{data-model.cds → schema.cds} +0 -0
@@ -1,2448 +0,0 @@
1
- /**
2
- * Converts OData CSDL JSON to OpenAPI 3.0.2
3
- */
4
-
5
- var pluralize = require('pluralize')
6
-
7
- //TODO
8
- // - Core.Example for complex types
9
- // - reduce number of loops over schemas
10
- // - inject $$name into each model element to make parameter passing easier?
11
- // - allow passing additional files for referenced documents
12
- // - delta: headers Prefer and Preference-Applied
13
- // - inline definitions for Edm.* to make OpenAPI documents self-contained
14
- // - both "clickable" and freestyle $expand, $select, $orderby - does not work yet, open issue for OpenAPI UI
15
- // - system query options for actions/functions/imports depending on $Collection
16
- // - 200 response for PATCH
17
- // - ETag for GET / If-Match for PATCH and DELETE depending on @Core.OptimisticConcurrency
18
- // - CountRestrictions for GET collection-valued (containment) navigation - https://issues.oasis-open.org/browse/ODATA-1300
19
- // - InsertRestrictions/NonInsertableProperties
20
- // - InsertRestrictions/NonInsertableNavigationProperties
21
- // - see //TODO comments below
22
-
23
- const SUFFIX = {
24
- read: "",
25
- create: "-create",
26
- update: "-update",
27
- };
28
-
29
- const TITLE_SUFFIX = {
30
- "": "",
31
- "-create": " (for create)",
32
- "-update": " (for update)",
33
- };
34
-
35
- /**
36
- * ODM annotations in CDS that should be converted into OpenAPI.
37
- */
38
- const ODM_ANNOTATIONS = Object.freeze(
39
- {
40
- '@ODM.entityName': 'x-sap-odm-entity-name',
41
- '@ODM.oid': 'x-sap-odm-oid'
42
- });
43
-
44
- /**
45
- * Construct an OpenAPI description from a CSDL document
46
- * @param {object} csdl CSDL document
47
- * @param {object} options Optional parameters
48
- * @return {object} OpenAPI description
49
- */
50
- module.exports.csdl2openapi = function (
51
- csdl,
52
- {
53
- url: serviceRoot,
54
- servers: serversObject,
55
- odataVersion: odataVersion,
56
- scheme: scheme = 'https',
57
- host: host = 'localhost',
58
- basePath: basePath = '/service-root',
59
- diagram: diagram = false,
60
- maxLevels: maxLevels = 5
61
- } = {}
62
- ) {
63
- // as preProcess below mutates the csdl, copy it before, to avoid side-effects on the caller side
64
- csdl = JSON.parse(JSON.stringify(csdl))
65
- csdl.$Version = odataVersion ? odataVersion : '4.01'
66
- serviceRoot = serviceRoot || (scheme + '://' + host + basePath);
67
- const queryOptionPrefix = csdl.$Version <= '4.01' ? '$' : '';
68
- const typesToInline = {}; // filled in schema() and used in inlineTypes()
69
- const boundOverloads = {};
70
- const derivedTypes = {};
71
- const alias = {};
72
- const namespace = { 'Edm': 'Edm' };
73
- const namespaceUrl = {};
74
- const voc = {};
75
- const requiredSchemas = { list: [], used: {} };
76
-
77
- preProcess(csdl, boundOverloads, derivedTypes, alias, namespace, namespaceUrl, voc);
78
-
79
- const entityContainer = csdl.$EntityContainer ? modelElement(csdl.$EntityContainer) : {};
80
- if (csdl.$EntityContainer) {
81
- let serviceName = nameParts(csdl.$EntityContainer).qualifier;
82
- Object.keys(entityContainer).forEach(element => {
83
- if (entityContainer[element].$Type) {
84
- let type = nameParts(entityContainer[element].$Type).name;
85
- if (csdl[serviceName]?.[type]?.['@cds.autoexpose'] || csdl[serviceName]?.[type]?.['@cds.autoexposed'])
86
- entityContainer[element]['$cds.autoexpose'] = true;
87
- }
88
- });
89
- }
90
-
91
- const keyAsSegment = entityContainer ? entityContainer[voc.Capabilities.KeyAsSegmentSupported] : {};
92
-
93
- const openapi = {
94
- openapi: '3.0.2',
95
- info: getInfo(csdl, entityContainer),
96
- 'x-sap-api-type': 'ODATAV4',
97
- 'x-odata-version': csdl.$Version,
98
- 'x-sap-shortText': getShortText(csdl, entityContainer),
99
- servers: getServers(serviceRoot, serversObject),
100
- tags: entityContainer ? getTags(entityContainer) : {},
101
- paths: entityContainer ? getPaths(entityContainer) : {},
102
- components: getComponents(csdl, entityContainer)
103
- };
104
- if (!csdl.$EntityContainer) {
105
- delete openapi.servers;
106
- delete openapi.tags;
107
- }
108
-
109
- security(openapi, entityContainer);
110
-
111
- return openapi;
112
-
113
-
114
- /**
115
- * Collect model info for easier lookup
116
- * @param {object} csdl CSDL document
117
- * @param {object} boundOverloads Map of action/function names to bound overloads
118
- * @param {object} derivedTypes Map of type names to derived types
119
- * @param {object} alias Map of namespace or alias to alias
120
- * @param {object} namespace Map of namespace or alias to namespace
121
- * @param {object} namespaceUrl Map of namespace to reference URL
122
- * @param {object} voc Map of vocabularies and terms
123
- */
124
- function preProcess(csdl, boundOverloads, derivedTypes, alias, namespace, namespaceUrl, voc) {
125
- Object.keys(csdl.$Reference || {}).forEach(url => {
126
- const reference = csdl.$Reference[url];
127
- (reference.$Include || []).forEach(include => {
128
- const qualifier = include.$Alias || include.$Namespace;
129
- alias[include.$Namespace] = qualifier;
130
- namespace[qualifier] = include.$Namespace;
131
- namespace[include.$Namespace] = include.$Namespace;
132
- namespaceUrl[include.$Namespace] = url;
133
- });
134
- });
135
-
136
- getVocabularies(voc, alias);
137
-
138
- Object.keys(csdl).filter(name => isIdentifier(name)).forEach(name => {
139
- const schema = csdl[name];
140
- const qualifier = schema.$Alias || name;
141
- const isDefaultNamespace = schema[voc.Core.DefaultNamespace];
142
-
143
- alias[name] = qualifier;
144
- namespace[qualifier] = name;
145
- namespace[name] = name;
146
-
147
- Object.keys(schema).filter(iName => isIdentifier(iName)).forEach(iName2 => {
148
- const qualifiedName = qualifier + '.' + iName2;
149
- const element = schema[iName2];
150
- if (Array.isArray(element)) {
151
- element.filter(overload => overload.$IsBound).forEach(overload => {
152
- const type = overload.$Parameter[0].$Type + (overload.$Parameter[0].$Collection ? '-c' : '');
153
- if (!boundOverloads[type]) boundOverloads[type] = [];
154
- boundOverloads[type].push({ name: (isDefaultNamespace ? iName2 : qualifiedName), overload: overload });
155
- });
156
- } else if (element.$BaseType) {
157
- const base = namespaceQualifiedName(element.$BaseType);
158
- if (!derivedTypes[base]) derivedTypes[base] = [];
159
- derivedTypes[base].push(qualifiedName);
160
- }
161
- });
162
-
163
- Object.keys(schema.$Annotations || {}).forEach(target => {
164
- const annotations = schema.$Annotations[target];
165
- const segments = target.split('/');
166
- const open = segments[0].indexOf('(');
167
- let element;
168
- if (open == -1) {
169
- element = modelElement(segments[0]);
170
- } else {
171
- element = modelElement(segments[0].substring(0, open));
172
- let args = segments[0].substring(open + 1, segments[0].length - 1);
173
- element = element.find(
174
- (overload) =>
175
- (overload.$Kind == "Action" &&
176
- overload.$IsBound != true &&
177
- args == "") ||
178
- (overload.$Kind == "Action" &&
179
- args ==
180
- (overload.$Parameter[0].$Collection
181
- ? `Collection(${overload.$Parameter[0].$Type})`
182
- : overload.$Parameter[0].$Type || "")) ||
183
- (overload.$Parameter || [])
184
- .map((p) => {
185
- const type = p.$Type || "Edm.String";
186
- return p.$Collection ? `Collection(${type})` : type;
187
- })
188
- .join(",") == args
189
- );
190
- }
191
- if (!element) {
192
- console.warn(`Invalid annotation target '${target}'`);
193
- } else if (Array.isArray(element)) {
194
- //TODO: action or function:
195
- //- loop over all overloads
196
- //- if there are more segments, a parameter or the return type is targeted
197
- } else {
198
- switch (segments.length) {
199
- case 1:
200
- Object.assign(element, annotations);
201
- break;
202
- case 2:
203
- if (['Action', 'Function'].includes(element.$Kind)) {
204
- if (segments[1] == '$ReturnType') {
205
- if (element.$ReturnType)
206
- Object.assign(element.$ReturnType, annotations);
207
- } else {
208
- const parameter = element.$Parameter.find(p => p.$Name == segments[1]);
209
- Object.assign(parameter, annotations);
210
- }
211
- } else {
212
- if (element[segments[1]]) {
213
- Object.assign(element[segments[1]], annotations);
214
- } else {
215
- // console.warn(`Invalid annotation target '${target}'`)
216
- }
217
- }
218
- break;
219
- default:
220
- console.warn('More than two annotation target path segments');
221
- }
222
- }
223
- });
224
- });
225
- }
226
-
227
- /**
228
- * Construct map of qualified term names
229
- * @param {object} voc Map of vocabularies and terms
230
- * @param {object} alias Map of namespace or alias to alias
231
- */
232
- function getVocabularies(voc, alias) {
233
- const terms = {
234
- Authorization: ['Authorizations', 'SecuritySchemes'],
235
- Capabilities: ['BatchSupport', 'BatchSupported', 'ChangeTracking', 'CountRestrictions', 'DeleteRestrictions', 'DeepUpdateSupport', 'ExpandRestrictions',
236
- 'FilterRestrictions', 'IndexableByKey', 'InsertRestrictions', 'KeyAsSegmentSupported', 'NavigationRestrictions', 'OperationRestrictions',
237
- 'ReadRestrictions', 'SearchRestrictions', 'SelectSupport', 'SkipSupported', 'SortRestrictions', 'TopSupported', 'UpdateRestrictions'],
238
- Core: ['AcceptableMediaTypes', 'Computed', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription',
239
- 'OptionalParameter', 'Permissions', 'SchemaVersion'],
240
- JSON: ['Schema'],
241
- Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern']
242
- };
243
-
244
- Object.keys(terms).forEach(vocab => {
245
- voc[vocab] = {};
246
- terms[vocab].forEach(term => {
247
- if (alias['Org.OData.' + vocab + '.V1'] != undefined)
248
- voc[vocab][term] = '@' + alias['Org.OData.' + vocab + '.V1'] + '.' + term;
249
- });
250
- });
251
-
252
- voc.Common = {
253
- Label: `@${alias['com.sap.vocabularies.Common.v1']}.Label`
254
- }
255
- }
256
-
257
- /**
258
- * Construct the Info Object
259
- * @param {object} csdl CSDL document
260
- * @param {object} entityContainer Entity Container object
261
- * @return {object} Info Object
262
- */
263
- function getInfo(csdl, entityContainer) {
264
- const namespace = csdl.$EntityContainer ? nameParts(csdl.$EntityContainer).qualifier : null;
265
- const containerSchema = csdl.$EntityContainer ? csdl[namespace] : {};
266
- let description;
267
- if (entityContainer && entityContainer[voc.Core.LongDescription]) {
268
- description = entityContainer[voc.Core.LongDescription];
269
- }
270
- else if (containerSchema && containerSchema[voc.Core.LongDescription]) {
271
- description = containerSchema[voc.Core.LongDescription];
272
- }
273
- else {
274
- description = ('This service is located at [' + serviceRoot + '/]('
275
- + serviceRoot.replace(/\(/g, '%28').replace(/\)/g, '%29') + '/)');
276
- }
277
- description += (diagram ? getResourceDiagram(csdl, entityContainer) : '');
278
- let title;
279
- if (entityContainer && entityContainer[voc.Common.Label]) {
280
- title = entityContainer[voc.Common.Label];
281
- }
282
- else if (entityContainer && entityContainer[voc.Core.Description]) {
283
- title = entityContainer[voc.Core.Description];
284
- }
285
- else if (containerSchema && containerSchema[voc.Core.Description]) {
286
- title = containerSchema[voc.Core.Description];
287
- }
288
- else if (csdl.$EntityContainer) {
289
- title = 'Service for namespace ' + namespace;
290
- }
291
- else {
292
- title = 'OData CSDL document';
293
- }
294
- return {
295
- title: title,
296
- description: csdl.$EntityContainer ? description : '',
297
- version: containerSchema[voc.Core.SchemaVersion] || ''
298
- };
299
- }
300
-
301
- /**
302
- * Construct resource diagram using web service at https://yuml.me
303
- * @param {object} csdl CSDL document
304
- * @param {object} entityContainer Entity Container object
305
- * @return {string} resource diagram
306
- */
307
- function getResourceDiagram(csdl, entityContainer) {
308
- let diagram = '';
309
- let comma = '';
310
- //TODO: make colors configurable
311
- let color = { resource: '{bg:lawngreen}', entityType: '{bg:lightslategray}', complexType: '', external: '{bg:whitesmoke}' }
312
-
313
- Object.keys(csdl).filter(name => isIdentifier(name)).forEach(namespace => {
314
- const schema = csdl[namespace];
315
- Object.keys(schema).filter(name => isIdentifier(name) && ['EntityType', 'ComplexType'].includes(schema[name].$Kind))
316
- .forEach(typeName => {
317
- const type = schema[typeName];
318
- diagram += comma
319
- + (type.$BaseType ? '[' + nameParts(type.$BaseType).name + ']^' : '')
320
- + '[' + typeName + (type.$Kind == 'EntityType' ? color.entityType : color.complexType) + ']';
321
- Object.keys(type).filter(name => isIdentifier(name)).forEach(propertyName => {
322
- const property = type[propertyName];
323
- const targetNP = nameParts(property.$Type || 'Edm.String');
324
- if (property.$Kind == 'NavigationProperty' || targetNP.qualifier != 'Edm') {
325
- const target = modelElement(property.$Type);
326
- const bidirectional = property.$Partner && target && target[property.$Partner] && target[property.$Partner].$Partner == propertyName;
327
- // Note: if the partner has the same name then it will also be depicted
328
- if (!bidirectional || propertyName <= property.$Partner) {
329
- diagram += ',[' + typeName + ']'
330
- + ((property.$Kind != 'NavigationProperty' || property.$ContainsTarget) ? '++' : (bidirectional ? cardinality(target[property.$Partner]) : ''))
331
- + '-'
332
- + cardinality(property)
333
- + ((property.$Kind != 'NavigationProperty' || bidirectional) ? '' : '>')
334
- + '['
335
- + (target ? targetNP.name : property.$Type + color.external)
336
- + ']';
337
- }
338
- }
339
- });
340
- comma = ',';
341
- });
342
- });
343
-
344
- Object.keys(entityContainer).filter(name => isIdentifier(name)).reverse().forEach(name => {
345
- const resource = entityContainer[name];
346
- if (resource.$Type) {
347
- diagram += comma
348
- + '[' + name + '%20' + color.resource + ']' // additional space in case entity set and type have same name
349
- + '++-'
350
- + cardinality(resource)
351
- + '>[' + nameParts(resource.$Type).name + ']';
352
- } else {
353
- if (resource.$Action) {
354
- diagram += comma
355
- + '[' + name + color.resource + ']';
356
- const overload = modelElement(resource.$Action).find(pOverload => !pOverload.$IsBound);
357
- diagram += overloadDiagram(name, overload);
358
- } else if (resource.$Function) {
359
- diagram += comma
360
- + '[' + name + color.resource + ']';
361
- const overloads = modelElement(resource.$Function);
362
- if (overloads) {
363
- const unbound = overloads.filter(overload => !overload.$IsBound);
364
- // TODO: loop over all overloads, add new source box after first arrow
365
- diagram += overloadDiagram(name, unbound[0]);
366
- }
367
- }
368
- }
369
- });
370
-
371
- if (diagram != '') {
372
- diagram = '\n\n## Entity Data Model\n![ER Diagram](https://yuml.me/diagram/class/'
373
- + diagram
374
- + ')\n\n### Legend\n![Legend](https://yuml.me/diagram/plain;dir:TB;scale:60/class/[External.Type' + color.external
375
- + '],[ComplexType' + color.complexType + '],[EntityType' + color.entityType
376
- + '],[EntitySet/Singleton/Operation' + color.resource + '])';
377
- }
378
-
379
- return diagram;
380
-
381
- /**
382
- * Diagram representation of property cardinality
383
- * @param {object} typedElement Typed model element, e.g. property
384
- * @return {string} cardinality
385
- */
386
- function cardinality(typedElement) {
387
- return typedElement.$Collection ? '*' : (typedElement.$Nullable ? '0..1' : '');
388
- }
389
-
390
- /**
391
- * Diagram representation of action or function overload
392
- * @param {string} name Name of action or function import
393
- * @param {object} overload Action or function overload
394
- * @return {string} diagram part
395
- */
396
- function overloadDiagram(name, overload) {
397
- let diag = "";
398
- if (overload.$ReturnType) {
399
- const type = modelElement(overload.$ReturnType.$Type || "Edm.String");
400
- if (type) {
401
- diag += "-" + cardinality(overload.$ReturnType) + ">[" + nameParts(overload.$ReturnType.$Type).name + "]";
402
- }
403
- }
404
- for (const param of overload.$Parameter || []) {
405
- const type = modelElement(param.$Type || "Edm.String");
406
- if (type) {
407
- diag += comma + "[" + name + color.resource + "]in-" + cardinality(param.$Type) + ">[" + nameParts(param.$Type).name + "]";
408
- }
409
- }
410
- return diag;
411
- }
412
- }
413
-
414
- /**
415
- * Find model element by qualified name
416
- * @param {string} qname Qualified name of model element
417
- * @return {object} Model element
418
- */
419
- function modelElement(qname) {
420
- const q = nameParts(qname);
421
- const schema = csdl[q.qualifier] || csdl[namespace[q.qualifier]];
422
- return schema ? schema[q.name] : null;
423
- }
424
-
425
- /**
426
- * Construct the short text
427
- * @param {object} csdl CSDL document
428
- * @param {object} entityContainer Entity Container object
429
- * @return {string} short text
430
- */
431
- function getShortText(csdl, entityContainer) {
432
- const namespace = csdl.$EntityContainer ? nameParts(csdl.$EntityContainer).qualifier : null;
433
- const containerSchema = csdl.$EntityContainer ? csdl[namespace] : {};
434
- let shortText;
435
- if (entityContainer && entityContainer[voc.Core.Description]) {
436
- shortText = entityContainer[voc.Core.Description];
437
- }
438
- else if (containerSchema && containerSchema[voc.Core.Description]) {
439
- shortText = containerSchema[voc.Core.Description];
440
- }
441
- else if (csdl.$EntityContainer) {
442
- shortText = 'Service for namespace ' + namespace;
443
- }
444
- else {
445
- shortText = 'OData CSDL document';
446
- }
447
- return shortText;
448
- }
449
-
450
- /**
451
- * Construct an array of Server Objects
452
- * @param {object} serviceRoot The service root
453
- * @param {object} serversObject Input servers object
454
- * @return {Array} The list of servers
455
- */
456
- function getServers(serviceRoot, serversObject) {
457
- let servers;
458
-
459
- if (serversObject) {
460
- try {
461
- servers = JSON.parse(serversObject);
462
- } catch (err) {
463
- throw new Error(`The input server object is invalid.`);
464
- }
465
-
466
- if (!servers.length) {
467
- throw new Error(`The input server object should be an array.`);
468
- }
469
- } else {
470
- servers = [{ url: serviceRoot }];
471
- }
472
- return servers;
473
- }
474
-
475
- /**
476
- * Construct an array of Tag Objects from the entity container
477
- * @param {object} container The entity container
478
- * @return {Array} The list of tags
479
- */
480
- function getTags(container) {
481
- const tags = [];
482
- // all entity sets and singletons
483
- Object.keys(container).filter(name => isIdentifier(name) && container[name].$Type).forEach(child => {
484
- const type = modelElement(container[child].$Type) || {};
485
- const tag = {
486
- name: type[voc.Common.Label] || child
487
- };
488
- const description = container[child][voc.Core.Description] || type[voc.Core.Description];
489
- if (description) tag.description = description;
490
- tags.push(tag);
491
- })
492
- return tags.sort(function (pre, next) { return pre.name.localeCompare(next.name) });
493
- }
494
-
495
- /**
496
- * Construct the Paths Object from the entity container
497
- * @param {object} container Entity container
498
- * @return {object} Paths Object
499
- */
500
- function getPaths(container) {
501
- const paths = {};
502
- const resources = Object.keys(container).filter(name => isIdentifier(name));
503
- resources.forEach(name => {
504
- let child = container[name];
505
- if (child.$Type) {
506
- const type = modelElement(child.$Type);
507
- const sourceName = (type && type[voc.Common.Label]) || name;
508
- // entity sets and singletons are almost containment navigation properties
509
- child.$ContainsTarget = true;
510
- pathItems(paths, '/' + name, [], child, child, sourceName, sourceName, child, 0, '');
511
- } else if (child.$Action) {
512
- pathItemActionImport(paths, name, child);
513
- } else if (child.$Function) {
514
- pathItemFunctionImport(paths, name, child);
515
- } else {
516
- console.warn('Unrecognized entity container child: ' + name);
517
- }
518
- })
519
- if (resources.length > 0) pathItemBatch(paths, container);
520
- return Object.keys(paths).sort().reduce((p, c) => (p[c] = paths[c], p), {});
521
- }
522
-
523
- /**
524
- * Add path and Path Item Object for a navigation segment
525
- * @param {object} paths Paths Object to augment
526
- * @param {string} prefix Prefix for path
527
- * @param {Array} prefixParameters Parameter Objects for prefix
528
- * @param {object} element Model element of navigation segment
529
- * @param {object} root Root model element
530
- * @param {string} sourceName Name of path source
531
- * @param {string} targetName Name of path target
532
- * @param {string} target Target container child of path
533
- * @param {integer} level Number of navigation segments so far
534
- * @param {string} navigationPath Path for finding navigation restrictions
535
- */
536
- function pathItems(paths, prefix, prefixParameters, element, root, sourceName, targetName, target, level, navigationPath) {
537
- const name = prefix.substring(prefix.lastIndexOf('/') + 1);
538
- const type = modelElement(element.$Type);
539
- const pathItem = {};
540
- const restrictions = navigationPropertyRestrictions(root, navigationPath);
541
- const nonExpandable = nonExpandableProperties(root, navigationPath);
542
-
543
- paths[prefix] = pathItem;
544
- if (prefixParameters.length > 0) pathItem.parameters = prefixParameters;
545
-
546
- operationRead(pathItem, element, name, sourceName, targetName, target, level, restrictions, false, nonExpandable);
547
- if (!root['$cds.autoexpose'] && element.$Collection && (element.$ContainsTarget || level < 2 && target)) {
548
- operationCreate(pathItem, element, name, sourceName, targetName, target, level, restrictions);
549
- }
550
- pathItemsForBoundOperations(paths, prefix, prefixParameters, element, sourceName);
551
-
552
- if (element.$ContainsTarget) {
553
- if (element.$Collection) {
554
- if (level < maxLevels)
555
- pathItemsWithKey(paths, prefix, prefixParameters, element, root, sourceName, targetName, target, level, navigationPath, restrictions, nonExpandable);
556
- } else {
557
- if (!root['$cds.autoexpose']) {
558
- operationUpdate(pathItem, element, name, sourceName, target, level, restrictions);
559
- if (element.$Nullable) {
560
- operationDelete(pathItem, element, name, sourceName, target, level, restrictions);
561
- }
562
- }
563
- pathItemsForBoundOperations(paths, prefix, prefixParameters, element, sourceName);
564
- pathItemsWithNavigation(paths, prefix, prefixParameters, type, root, sourceName, level, navigationPath);
565
- }
566
- }
567
-
568
- if (Object.keys(pathItem).filter((i) => i !== "parameters").length === 0)
569
- delete paths[prefix];
570
- }
571
-
572
- /**
573
- * Find navigation restrictions for a navigation path
574
- * @param {object} root Root model element
575
- * @param {string} navigationPath Path for finding navigation restrictions
576
- * @return Navigation property restrictions of navigation segment
577
- */
578
- function navigationPropertyRestrictions(root, navigationPath) {
579
- const navigationRestrictions = root[voc.Capabilities.NavigationRestrictions] || {};
580
- return (navigationRestrictions.RestrictedProperties || []).find(item => navigationPropertyPath(item.NavigationProperty) == navigationPath)
581
- || {};
582
- }
583
-
584
- /**
585
- * Find non-expandable properties for a navigation path
586
- * @param {object} root Root model element
587
- * @param {string} navigationPath Path for finding navigation restrictions
588
- * @return Navigation property restrictions of navigation segment
589
- */
590
- function nonExpandableProperties(root, navigationPath) {
591
- const expandRestrictions = root[voc.Capabilities.ExpandRestrictions] || {};
592
- const prefix = navigationPath.length === 0 ? '' : navigationPath + '/'
593
- const from = prefix.length
594
- const nonExpandable = []
595
- for (const path of (expandRestrictions.NonExpandableProperties || [])) {
596
- if (path.startsWith(prefix)) {
597
- nonExpandable.push(path.substring(from))
598
- }
599
- }
600
- return nonExpandable;
601
- }
602
-
603
- /**
604
- * Add path and Path Item Object for a navigation segment with key
605
- * @param {object} paths Paths Object to augment
606
- * @param {string} prefix Prefix for path
607
- * @param {Array} prefixParameters Parameter Objects for prefix
608
- * @param {object} element Model element of navigation segment
609
- * @param {object} root Root model element
610
- * @param {string} sourceName Name of path source
611
- * @param {string} targetName Name of path target
612
- * @param {string} target Target container child of path
613
- * @param {integer} level Number of navigation segments so far
614
- * @param {string} navigationPath Path for finding navigation restrictions
615
- * @param {object} restrictions Navigation property restrictions of navigation segment
616
- * @param {array} nonExpandable Non-expandable navigation properties
617
- */
618
- function pathItemsWithKey(paths, prefix, prefixParameters, element, root, sourceName, targetName, target, level, navigationPath, restrictions, nonExpandable) {
619
- const targetIndexable = target == null || target[voc.Capabilities.IndexableByKey] != false;
620
- if (restrictions.IndexableByKey == true || restrictions.IndexableByKey != false && targetIndexable) {
621
- const name = prefix.substring(prefix.lastIndexOf('/') + 1);
622
- const type = modelElement(element.$Type);
623
- const key = entityKey(type, level);
624
- if (key.parameters.length > 0) {
625
- const path = prefix + key.segment;
626
- const parameters = prefixParameters.concat(key.parameters);
627
- const pathItem = { parameters: parameters };
628
- paths[path] = pathItem;
629
-
630
- operationRead(pathItem, element, name, sourceName, targetName, target, level, restrictions, true, nonExpandable);
631
- if (!root['$cds.autoexpose']) {
632
- operationUpdate(pathItem, element, name, sourceName, target, level, restrictions, true);
633
- operationDelete(pathItem, element, name, sourceName, target, level, restrictions, true);
634
- }
635
- if (Object.keys(pathItem).filter((i) => i !== "parameters").length === 0)
636
- delete paths[path];
637
-
638
- pathItemsForBoundOperations(paths, path, parameters, element, sourceName, true);
639
- pathItemsWithNavigation(paths, path, parameters, type, root, sourceName, level, navigationPath);
640
- }
641
- }
642
- }
643
-
644
- /**
645
- * Construct Operation Object for create
646
- * @param {object} pathItem Path Item Object to augment
647
- * @param {object} element Model element of navigation segment
648
- * @param {string} name Name of navigation segment
649
- * @param {string} sourceName Name of path source
650
- * @param {string} targetName Name of path target
651
- * @param {string} target Target container child of path
652
- * @param {integer} level Number of navigation segments so far
653
- * @param {object} restrictions Navigation property restrictions of navigation segment
654
- */
655
- function operationCreate(pathItem, element, name, sourceName, targetName, target, level, restrictions) {
656
- const insertRestrictions = restrictions.InsertRestrictions || target && target[voc.Capabilities.InsertRestrictions] || {};
657
- let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false); // count property will be added if CountRestrictions is false
658
- if (insertRestrictions.Insertable !== false) {
659
- const lname = pluralize.singular(splitName(name));
660
- const type = modelElement(element.$Type);
661
- pathItem.post = {
662
- summary: insertRestrictions.Description || operationSummary('Creates', name, sourceName, level, true, true),
663
- tags: [sourceName],
664
- requestBody: {
665
- description: type && type[voc.Core.Description] || 'New ' + lname,
666
- required: true,
667
- content: {
668
- 'application/json': {
669
- schema: ref(element.$Type, SUFFIX.create),
670
- }
671
- }
672
- },
673
- responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses, !countRestrictions),
674
- };
675
- if (insertRestrictions.LongDescription) pathItem.post.description = insertRestrictions.LongDescription;
676
- if (targetName && sourceName != targetName) pathItem.post.tags.push(targetName);
677
- customParameters(pathItem.post, insertRestrictions);
678
- }
679
- }
680
-
681
- /**
682
- * Split camel-cased name into words
683
- * @param {string} name Name to split
684
- * @return {string} Split name
685
- */
686
- function splitName(name) {
687
- return name.split(/(?=[A-Z])/g).join(' ').toLowerCase().replace(/ i d/g, ' id');
688
- }
689
-
690
- /**
691
- * Construct operation summary
692
- * @param {string} operation Operation (verb)
693
- * @param {string} name Name of navigation segment
694
- * @param {string} sourceName Name of path source
695
- * @param {integer} level Number of navigation segments so far
696
- * @param {boolean} collection Access a collection
697
- * @param {boolean} byKey Access by key
698
- * @return {string} Operation Text
699
- */
700
- function operationSummary(operation, name, sourceName, level, collection, byKey) {
701
- let lname = splitName(name);
702
- let sname = splitName(sourceName);
703
-
704
- return operation + ' '
705
- + (byKey ? 'a single ' : (collection ? 'a list of ' : ''))
706
- + (byKey ? pluralize.singular(lname) : lname)
707
- //TODO: suppress "a" for all singletons
708
- + (level == 0 ? '' : (level == 1 && sname == 'me' ? ' of me' : ' of a ' + pluralize.singular(sname)))
709
- + '.'
710
- }
711
-
712
- /**
713
- * Construct Operation Object for read
714
- * @param {object} pathItem Path Item Object to augment
715
- * @param {object} element Model element of navigation segment
716
- * @param {string} name Name of navigation segment
717
- * @param {string} sourceName Name of path source
718
- * @param {string} targetName Name of path target
719
- * @param {string} target Target container child of path
720
- * @param {integer} level Number of navigation segments so far
721
- * @param {object} restrictions Navigation property restrictions of navigation segment
722
- * @param {boolean} byKey Read by key
723
- * @param {array} nonExpandable Non-expandable navigation properties
724
- */
725
- function operationRead(pathItem, element, name, sourceName, targetName, target, level, restrictions, byKey, nonExpandable) {
726
- const targetRestrictions = target?.[voc.Capabilities.ReadRestrictions];
727
- const readRestrictions = restrictions.ReadRestrictions || targetRestrictions || {};
728
- const readByKeyRestrictions = readRestrictions.ReadByKeyRestrictions;
729
- let readable = true;
730
- let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
731
- if (byKey && readByKeyRestrictions && readByKeyRestrictions.Readable !== undefined)
732
- readable = readByKeyRestrictions.Readable;
733
- else if (readRestrictions.Readable !== undefined)
734
- readable = readRestrictions.Readable;
735
-
736
- if (readable) {
737
- let descriptions = (level == 0 ? targetRestrictions : restrictions.ReadRestrictions) || {};
738
- if (byKey) descriptions = descriptions.ReadByKeyRestrictions || {};
739
- const lname = splitName(name);
740
- const collection = !byKey && element.$Collection;
741
- const operation = {
742
- summary: descriptions.Description || operationSummary('Retrieves', name, sourceName, level, element.$Collection, byKey),
743
- tags: [sourceName],
744
- parameters: [],
745
- responses: response(200, 'Retrieved ' + (byKey ? pluralize.singular(lname) : lname), { $Type: element.$Type, $Collection: collection },
746
- byKey ? readByKeyRestrictions?.ErrorResponses : readRestrictions?.ErrorResponses, !countRestrictions)
747
- };
748
- const deltaSupported = element[voc.Capabilities.ChangeTracking] && element[voc.Capabilities.ChangeTracking].Supported;
749
- if (!byKey && deltaSupported) {
750
- operation.responses[200].content['application/json'].schema.properties['@odata.deltaLink'] = {
751
- type: 'string',
752
- example: basePath + '/' + name + '?$deltatoken=opaque server-generated token for fetching the delta'
753
- }
754
- }
755
- if (descriptions.LongDescription) operation.description = descriptions.LongDescription;
756
- if (target && sourceName != targetName) operation.tags.push(targetName);
757
- customParameters(operation, byKey ? readByKeyRestrictions || readRestrictions : readRestrictions);
758
-
759
- if (collection) {
760
- optionTop(operation.parameters, target, restrictions);
761
- optionSkip(operation.parameters, target, restrictions);
762
- if (csdl.$Version >= '4.0') optionSearch(operation.parameters, target, restrictions);
763
- optionFilter(operation.parameters, target, restrictions);
764
- optionCount(operation.parameters, target);
765
- optionOrderBy(operation.parameters, element, target, restrictions);
766
- }
767
-
768
- optionSelect(operation.parameters, element, target, restrictions);
769
- optionExpand(operation.parameters, element, target, nonExpandable);
770
-
771
- pathItem.get = operation;
772
- }
773
- }
774
-
775
- /**
776
- * Add custom headers and query options
777
- * @param {object} operation Operation object to augment
778
- * @param {object} restrictions Restrictions for operation
779
- */
780
- function customParameters(operation, restrictions) {
781
- if (
782
- !operation.parameters &&
783
- (restrictions.CustomHeaders || restrictions.CustomQueryOptions)
784
- )
785
- operation.parameters = [];
786
- for (const custom of restrictions.CustomHeaders || []) {
787
- operation.parameters.push(customParameter(custom, "header"));
788
- }
789
-
790
- for (const custom of restrictions.CustomQueryOptions || []) {
791
- operation.parameters.push(customParameter(custom, "query"));
792
- }
793
- }
794
-
795
- /**
796
- * Construct custom parameter
797
- * @param {object} custom custom parameter in OData format
798
- * @param {string} location "header" or "query"
799
- */
800
- function customParameter(custom, location) {
801
- return {
802
- name: custom.Name,
803
- in: location,
804
- required: custom.Required || false,
805
- ...(custom.Description && { description: custom.Description }),
806
- schema: {
807
- type: "string",
808
- ...(custom.DocumentationURL && {
809
- externalDocs: { url: custom.DocumentationURL },
810
- }),
811
- //TODO: Examples
812
- },
813
- };
814
- }
815
-
816
- /**
817
- * Add parameter for query option $count
818
- * @param {Array} parameters Array of parameters to augment
819
- * @param {string} target Target container child of path
820
- */
821
- function optionCount(parameters, target) {
822
- const targetRestrictions = target && target[voc.Capabilities.CountRestrictions];
823
- const targetCountable = target == null
824
- || targetRestrictions == null
825
- || targetRestrictions.Countable !== false;
826
-
827
- if (targetCountable) {
828
- parameters.push({
829
-
830
- $ref: '#/components/parameters/count'
831
- });
832
- }
833
- }
834
-
835
- /**
836
- * Add parameter for query option $expand
837
- * @param {Array} parameters Array of parameters to augment
838
- * @param {object} element Model element of navigation segment
839
- * @param {string} target Target container child of path
840
- * @param {array} nonExpandable Non-expandable navigation properties
841
- */
842
- function optionExpand(parameters, element, target, nonExpandable) {
843
- const targetRestrictions = target && target[voc.Capabilities.ExpandRestrictions];
844
- const supported = targetRestrictions == null || targetRestrictions.Expandable != false;
845
- if (supported) {
846
- const expandItems = ['*'].concat(navigationPaths(element).filter(path => !nonExpandable.includes(path)));
847
- if (expandItems.length > 1) {
848
- parameters.push({
849
- name: queryOptionPrefix + 'expand',
850
- description: (targetRestrictions && targetRestrictions[voc.Core.Description])
851
- || 'The value of $expand query option is a comma-separated list of navigation property names, \
852
- stream property names, or $value indicating the stream content of a media-entity. \
853
- The corresponding related entities and stream values will be represented inline, \
854
- see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)',
855
- in: 'query',
856
- explode: false,
857
- schema: {
858
- type: 'array',
859
- uniqueItems: true,
860
- items: {
861
- type: 'string',
862
- enum: expandItems
863
- }
864
- }
865
- });
866
- }
867
- }
868
- }
869
-
870
- /**
871
- * Collect navigation paths of a navigation segment and its potentially structured components
872
- * @param {object} element Model element of navigation segment
873
- * @param {string} prefix Navigation prefix
874
- * @param {integer} level Number of navigation segments so far
875
- * @return {Array} Array of navigation property paths
876
- */
877
- function navigationPaths(element, prefix = '', level = 0) {
878
- const paths = [];
879
- const type = modelElement(element.$Type);
880
- const properties = propertiesOfStructuredType(type);
881
- Object.keys(properties).forEach(key => {
882
- if (properties[key].$Kind == 'NavigationProperty') {
883
- paths.push(prefix + key)
884
- } else if (properties[key].$Type && level < maxLevels) {
885
- paths.push(...navigationPaths(properties[key], prefix + key + '/', level + 1));
886
- }
887
- })
888
- return paths;
889
- }
890
-
891
- /**
892
- * Add parameter for query option $filter
893
- * @param {Array} parameters Array of parameters to augment
894
- * @param {string} target Target container child of path
895
- * @param {object} restrictions Navigation property restrictions of navigation segment
896
- */
897
- function optionFilter(parameters, target, restrictions) {
898
- const filterRestrictions = restrictions.FilterRestrictions || target && target[voc.Capabilities.FilterRestrictions] || {};
899
-
900
- if (filterRestrictions.Filterable !== false) {
901
- const filter = {
902
- name: queryOptionPrefix + 'filter',
903
- description: filterRestrictions[voc.Core.Description]
904
- || 'Filter items by property values, see [Filtering](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionfilter)',
905
- in: 'query',
906
- schema: {
907
- type: 'string'
908
- }
909
- };
910
- if (filterRestrictions.RequiresFilter)
911
- filter.required = true;
912
- if (filterRestrictions.RequiredProperties) {
913
- filter.description += '\n\nRequired filter properties:';
914
- filterRestrictions.RequiredProperties.forEach(
915
- item => filter.description += '\n- ' + propertyPath(item)
916
- );
917
- }
918
- parameters.push(filter);
919
- }
920
- }
921
-
922
- /**
923
- * Add parameter for query option $orderby
924
- * @param {Array} parameters Array of parameters to augment
925
- * @param {object} element Model element of navigation segment
926
- * @param {string} target Target container child of path
927
- * @param {object} restrictions Navigation property restrictions of navigation segment
928
- */
929
- function optionOrderBy(parameters, element, target, restrictions) {
930
- const sortRestrictions = restrictions.SortRestrictions || target && target[voc.Capabilities.SortRestrictions] || {};
931
-
932
- if (sortRestrictions.Sortable !== false) {
933
- const nonSortable = {};
934
- (sortRestrictions.NonSortableProperties || []).forEach(name => {
935
- nonSortable[propertyPath(name)] = true;
936
- });
937
- const orderbyItems = [];
938
- primitivePaths(element).filter(property => !nonSortable[property]).forEach(property => {
939
- orderbyItems.push(property);
940
- orderbyItems.push(property + ' desc');
941
- });
942
- if (orderbyItems.length > 0) {
943
- parameters.push({
944
- name: queryOptionPrefix + 'orderby',
945
- description: sortRestrictions[voc.Core.Description]
946
- || 'Order items by property values, see [Sorting](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionorderby)',
947
- in: 'query',
948
- explode: false,
949
- schema: {
950
- type: 'array',
951
- uniqueItems: true,
952
- items: {
953
- type: 'string',
954
- enum: orderbyItems
955
- }
956
- }
957
- });
958
- }
959
- }
960
- }
961
-
962
- /**
963
- * Unpack EnumMember value if it uses CSDL JSON CS01 style, like CAP does
964
- * @param {string or object} path Qualified name of referenced type
965
- * @return {object} Reference Object
966
- */
967
- function enumMember(member) {
968
- if (typeof member == 'string')
969
- return member;
970
- else if (typeof member == 'object')
971
- return member.$EnumMember;
972
- }
973
-
974
- /**
975
- * Unpack NavigationPropertyPath value if it uses CSDL JSON CS01 style, like CAP does
976
- * @param {string or object} path Qualified name of referenced type
977
- * @return {object} Reference Object
978
- */
979
- function navigationPropertyPath(path) {
980
- if (typeof path == 'string')
981
- return path;
982
- else
983
- return path.$NavigationPropertyPath;
984
- }
985
-
986
- /**
987
- * Unpack PropertyPath value if it uses CSDL JSON CS01 style, like CAP does
988
- * @param {string or object} path Qualified name of referenced type
989
- * @return {object} Reference Object
990
- */
991
- function propertyPath(path) {
992
- if (typeof path == 'string')
993
- return path;
994
- else
995
- return path.$PropertyPath;
996
- }
997
-
998
- /**
999
- * Collect primitive paths of a navigation segment and its potentially structured components
1000
- * @param {object} element Model element of navigation segment
1001
- * @param {string} prefix Navigation prefix
1002
- * @return {Array} Array of primitive property paths
1003
- */
1004
- function primitivePaths(element, prefix = '') {
1005
- const paths = [];
1006
- const elementType = modelElement(element.$Type);
1007
-
1008
- if (!elementType) {
1009
- console.warn(`Unknown type for element: ${JSON.stringify(element)}`);
1010
- return paths;
1011
- }
1012
-
1013
- const propsOfType = propertiesOfStructuredType(elementType);
1014
- const ignore = Object.entries(propsOfType)
1015
- .filter(entry => entry[1].$Kind !== 'NavigationProperty')
1016
- .filter(entry => entry[1].$Type)
1017
- .filter(entry => nameParts(entry[1].$Type).qualifier !== 'Edm')
1018
- .filter(entry => !modelElement(entry[1].$Type));
1019
-
1020
- // Keep old logging
1021
- ignore.forEach(entry => console.warn(`Unknown type for element: ${JSON.stringify(entry)}`));
1022
-
1023
- const properties = Object.entries(propsOfType)
1024
- .filter(entry => entry[1].$Kind !== 'NavigationProperty')
1025
- .filter(entry => !ignore.includes(entry))
1026
- .map(entryToProperty({ path: prefix, typeRefChain: [] }));
1027
-
1028
- for (let i = 0; i < properties.length; i++) {
1029
- const property = properties[i];
1030
- if (!property.isComplex) {
1031
- paths.push(property.path);
1032
- continue;
1033
- }
1034
-
1035
- const typeRefChainTail = property.typeRefChain[property.typeRefChain.length - 1];
1036
-
1037
- // Allow full cycle to be shown (0) times
1038
- if (property.typeRefChain.filter(_type => _type === typeRefChainTail).length > 1) {
1039
- console.warn(`Cycle detected ${property.typeRefChain.join('->')}`);
1040
- continue;
1041
- }
1042
-
1043
- const expanded = Object.entries(property.properties)
1044
- .filter(pProperty => pProperty[1].$Kind !== 'NavigationProperty')
1045
- .map(entryToProperty(property))
1046
- properties.splice(i + 1, 0, ...expanded);
1047
- }
1048
-
1049
- return paths;
1050
- }
1051
-
1052
- function entryToProperty(parent) {
1053
-
1054
- return function (entry) {
1055
- const key = entry[0];
1056
- const property = entry[1];
1057
- const propertyType = property.$Type && modelElement(property.$Type);
1058
-
1059
- if (propertyType && propertyType.$Kind && propertyType.$Kind === 'ComplexType') {
1060
- return {
1061
- properties: propertiesOfStructuredType(propertyType),
1062
- path: `${parent.path}${key}/`,
1063
- typeRefChain: parent.typeRefChain.concat(property.$Type),
1064
- isComplex: true
1065
- }
1066
- }
1067
-
1068
- return {
1069
- properties: {},
1070
- path: `${parent.path}${key}`,
1071
- typeRefChain: [],
1072
- isComplex: false,
1073
- }
1074
- };
1075
- }
1076
-
1077
- /**
1078
- * Add parameter for query option $search
1079
- * @param {Array} parameters Array of parameters to augment
1080
- * @param {string} target Target container child of path
1081
- * @param {object} restrictions Navigation property restrictions of navigation segment
1082
- */
1083
- function optionSearch(parameters, target, restrictions) {
1084
- const searchRestrictions = restrictions.SearchRestrictions || target && target[voc.Capabilities.SearchRestrictions] || {};
1085
-
1086
- if (searchRestrictions.Searchable !== false) {
1087
- if (searchRestrictions[voc.Core.Description]) {
1088
- parameters.push({
1089
- name: queryOptionPrefix + 'search',
1090
- description: searchRestrictions[voc.Core.Description],
1091
- in: 'query',
1092
- schema: { type: 'string' }
1093
- });
1094
- } else {
1095
- parameters.push({ $ref: '#/components/parameters/search' });
1096
- }
1097
- }
1098
- }
1099
-
1100
- /**
1101
- * Add parameter for query option $select
1102
- * @param {Array} parameters Array of parameters to augment
1103
- * @param {object} element Model element of navigation segment
1104
- * @param {string} target Target container child of path
1105
- * @param {object} restrictions Navigation property restrictions of navigation segment
1106
- */
1107
- function optionSelect(parameters, element, target, restrictions) {
1108
- const selectSupport = restrictions.SelectSupport || target && target[voc.Capabilities.SelectSupport] || {};
1109
-
1110
- if (selectSupport.Supported !== false) {
1111
- const type = modelElement(element.$Type) || {};
1112
- const properties = propertiesOfStructuredType(type);
1113
- const selectItems = [];
1114
- Object.keys(properties).filter(key => properties[key].$Kind != 'NavigationProperty').forEach(
1115
- key => selectItems.push(key)
1116
- )
1117
- if (selectItems.length > 0) {
1118
- parameters.push({
1119
- name: queryOptionPrefix + 'select',
1120
- description: 'Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)',
1121
- in: 'query',
1122
- explode: false,
1123
- schema: {
1124
- type: 'array',
1125
- uniqueItems: true,
1126
- items: {
1127
- type: 'string',
1128
- enum: selectItems
1129
- }
1130
- }
1131
- });
1132
- }
1133
- }
1134
- }
1135
-
1136
- /**
1137
- * Add parameter for query option $skip
1138
- * @param {Array} parameters Array of parameters to augment
1139
- * @param {string} target Target container child of path
1140
- * @param {object} restrictions Navigation property restrictions of navigation segment
1141
- */
1142
- function optionSkip(parameters, target, restrictions) {
1143
- const supported = restrictions.SkipSupported !== undefined
1144
- ? restrictions.SkipSupported
1145
- : target == null || target[voc.Capabilities.SkipSupported] !== false;
1146
-
1147
- if (supported) {
1148
- parameters.push({
1149
- $ref: '#/components/parameters/skip'
1150
- });
1151
- }
1152
- }
1153
-
1154
- /**
1155
- * Add parameter for query option $top
1156
- * @param {Array} parameters Array of parameters to augment
1157
- * @param {string} target Target container child of path
1158
- * @param {object} restrictions Navigation property restrictions of navigation segment
1159
- */
1160
- function optionTop(parameters, target, restrictions) {
1161
- const supported = restrictions.TopSupported !== undefined
1162
- ? restrictions.TopSupported
1163
- : target == null || target[voc.Capabilities.TopSupported] !== false;
1164
-
1165
- if (supported) {
1166
- parameters.push({
1167
- $ref: '#/components/parameters/top'
1168
- });
1169
- }
1170
- }
1171
-
1172
- /**
1173
- * Construct Operation Object for update
1174
- * @param {object} pathItem Path Item Object to augment
1175
- * @param {object} element Model element of navigation segment
1176
- * @param {string} name Name of navigation segment
1177
- * @param {string} sourceName Name of path source
1178
- * @param {string} target Target container child of path
1179
- * @param {integer} level Number of navigation segments so far
1180
- * @param {object} restrictions Navigation property restrictions of navigation segment
1181
- * @param {boolean} byKey Update by key
1182
- */
1183
- function operationUpdate(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
1184
- const updateRestrictions = restrictions.UpdateRestrictions || target && target[voc.Capabilities.UpdateRestrictions] || {};
1185
- let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
1186
- if (updateRestrictions.Updatable !== false) {
1187
- const type = modelElement(element.$Type);
1188
- const operation = {
1189
- summary: updateRestrictions.Description || operationSummary('Changes', name, sourceName, level, element.$Collection, byKey),
1190
- tags: [sourceName],
1191
- requestBody: {
1192
- description: type && type[voc.Core.Description] || 'New property values',
1193
- required: true,
1194
- content: {
1195
- 'application/json': {
1196
- schema: ref(element.$Type, SUFFIX.update),
1197
- }
1198
- }
1199
- },
1200
- responses: response(204, "Success", undefined, updateRestrictions.ErrorResponses, !countRestrictions),
1201
- };
1202
- if (updateRestrictions.LongDescription) operation.description = updateRestrictions.LongDescription;
1203
- customParameters(operation, updateRestrictions);
1204
- const updateMethod = updateRestrictions.UpdateMethod ? updateRestrictions.UpdateMethod.toLowerCase() : "patch";
1205
- pathItem[updateMethod] = operation;
1206
- }
1207
- }
1208
-
1209
- /**
1210
- * Construct Operation Object for delete
1211
- * @param {object} pathItem Path Item Object to augment
1212
- * @param {object} element Model element of navigation segment
1213
- * @param {string} name Name of navigation segment
1214
- * @param {string} sourceName Name of path source
1215
- * @param {string} target Target container child of path
1216
- * @param {integer} level Number of navigation segments so far
1217
- * @param {object} restrictions Navigation property restrictions of navigation segment
1218
- * @param {boolean} byKey Delete by key
1219
- */
1220
- function operationDelete(pathItem, element, name, sourceName, target, level, restrictions, byKey) {
1221
- const deleteRestrictions = restrictions.DeleteRestrictions || target && target[voc.Capabilities.DeleteRestrictions] || {};
1222
- let countRestrictions = target && (target[voc.Capabilities.CountRestrictions]?.Countable === false);
1223
- if (deleteRestrictions.Deletable !== false) {
1224
- pathItem.delete = {
1225
- summary: deleteRestrictions.Description || operationSummary('Deletes', name, sourceName, level, element.$Collection, byKey),
1226
- tags: [sourceName],
1227
- responses: response(204, "Success", undefined, deleteRestrictions.ErrorResponses, !countRestrictions),
1228
- };
1229
- if (deleteRestrictions.LongDescription) pathItem.delete.description = deleteRestrictions.LongDescription;
1230
- customParameters(pathItem.delete, deleteRestrictions);
1231
- }
1232
- }
1233
-
1234
- /**
1235
- * Add paths and Path Item Objects for navigation segments
1236
- * @param {object} paths The Paths Object to augment
1237
- * @param {string} prefix Prefix for path
1238
- * @param {Array} prefixParameters Parameter Objects for prefix
1239
- * @param {object} type Entity type object of navigation segment
1240
- * @param {string} sourceName Name of path source
1241
- * @param {integer} level Number of navigation segments so far
1242
- * @param {string} navigationPrefix Path for finding navigation restrictions
1243
- */
1244
- function pathItemsWithNavigation(paths, prefix, prefixParameters, type, root, sourceName, level, navigationPrefix) {
1245
- const navigationRestrictions = root[voc.Capabilities.NavigationRestrictions] || {};
1246
- const rootNavigable = level == 0 && enumMember(navigationRestrictions.Navigability) != 'None'
1247
- || level == 1 && enumMember(navigationRestrictions.Navigability) != 'Single'
1248
- || level > 1;
1249
-
1250
- if (type && level < maxLevels) {
1251
- const properties = navigationPathMap(type);
1252
- Object.keys(properties).forEach(name => {
1253
- const parentRestrictions = navigationPropertyRestrictions(root, navigationPrefix);
1254
- if (enumMember(parentRestrictions.Navigability) == 'Single') return;
1255
-
1256
- const navigationPath = navigationPrefix + (navigationPrefix.length > 0 ? '/' : '') + name;
1257
- const restrictions = navigationPropertyRestrictions(root, navigationPath);
1258
- if (['Recursive', 'Single'].includes(enumMember(restrictions.Navigability))
1259
- || restrictions.Navigability == null && rootNavigable) {
1260
- const targetSetName = root.$NavigationPropertyBinding && root.$NavigationPropertyBinding[navigationPath];
1261
- const target = entityContainer[targetSetName];
1262
- const targetType = target && modelElement(target.$Type);
1263
- const targetName = (targetType && targetType[voc.Common.Label]) || targetSetName;
1264
- pathItems(paths, prefix + '/' + name, prefixParameters, properties[name], root, sourceName, targetName, target, level + 1, navigationPath);
1265
- }
1266
- });
1267
- }
1268
- }
1269
-
1270
- /**
1271
- * Collect navigation paths of a navigation segment and its potentially structured components
1272
- * @param {object} type Structured type
1273
- * @param {object} map Map of navigation property paths and their types
1274
- * @param {string} prefix Navigation prefix
1275
- * @param {integer} level Number of navigation segments so far
1276
- * @return {object} Map of navigation property paths and their types
1277
- */
1278
- function navigationPathMap(type, map = {}, prefix = '', level = 0) {
1279
- const properties = propertiesOfStructuredType(type);
1280
- Object.keys(properties).forEach(key => {
1281
- if (properties[key].$Kind == 'NavigationProperty') {
1282
- map[prefix + key] = properties[key];
1283
- } else if (properties[key].$Type && !properties[key].$Collection && level < maxLevels) {
1284
- navigationPathMap(modelElement(properties[key].$Type), map, prefix + key + '/', level + 1);
1285
- }
1286
- })
1287
- return map;
1288
- }
1289
-
1290
- /**
1291
- * Construct map of key names for an entity type
1292
- * @param {object} type Entity type object
1293
- * @return {object} Map of key names
1294
- */
1295
- function keyMap(type) {
1296
- const map = {};
1297
- if (type.$Kind == 'EntityType') {
1298
- const keys = getKey(type) || [];
1299
- keys.forEach(key => {
1300
- if (typeof key == 'string')
1301
- map[key] = true;
1302
- });
1303
- }
1304
- return map;
1305
- }
1306
-
1307
- /**
1308
- * Key for path item
1309
- * @param {object} entityType Entity Type object
1310
- * @return {array} Key of entity type or null
1311
- */
1312
- function getKey(entityType) {
1313
- let type = entityType;
1314
- let keys = null;
1315
- while (type) {
1316
- keys = type.$Key;
1317
- if (keys || !type.$BaseType) break;
1318
- type = modelElement(type.$BaseType);
1319
- }
1320
- return keys;
1321
- }
1322
-
1323
- /**
1324
- * Key for path item
1325
- * @param {object} entityType Entity Type object
1326
- * @param {integer} level Number of navigation segments so far
1327
- * @return {object} key: Key segment, parameters: key parameters
1328
- */
1329
- function entityKey(entityType, level) {
1330
- let segment = '';
1331
- const params = [];
1332
- const keys = getKey(entityType) || [];
1333
- const properties = propertiesOfStructuredType(entityType);
1334
-
1335
- keys.forEach((key, index) => {
1336
- const suffix = level > 0 ? '_' + level : '';
1337
- if (keyAsSegment)
1338
- segment += '/';
1339
- else {
1340
- if (index > 0) segment += ',';
1341
- if (keys.length != 1) segment += key + '=';
1342
- }
1343
- let parameter;
1344
- let property = {};
1345
- if (typeof key == 'string') {
1346
- parameter = key;
1347
- property = properties[key];
1348
- } else {
1349
- parameter = Object.keys(key)[0];
1350
- const segments = key[parameter].split('/');
1351
- property = properties[segments[0]];
1352
- for (let i = 1; i < segments.length; i++) {
1353
- const complexType = modelElement(property.$Type);
1354
- const properties = propertiesOfStructuredType(complexType);
1355
- property = properties[segments[i]];
1356
- }
1357
- }
1358
- const propertyType = property.$Type;
1359
- segment += pathValuePrefix(propertyType) + '{' + parameter + suffix + '}' + pathValueSuffix(propertyType);
1360
- const param = {
1361
- description: [property[voc.Core.Description], property[voc.Core.LongDescription]].filter(t => t).join(' \n')
1362
- || 'key: ' + parameter,
1363
- in: 'path',
1364
- name: parameter + suffix,
1365
- required: true,
1366
- schema: getSchema(property, '', true)
1367
- };
1368
- params.push(param);
1369
- })
1370
- return { segment: (keyAsSegment ? '' : '(') + segment + (keyAsSegment ? '' : ')'), parameters: params };
1371
- }
1372
-
1373
- /**
1374
- * Prefix for key value in key segment
1375
- * @param {typename} Qualified name of key property type
1376
- * @return {string} value prefix
1377
- */
1378
- function pathValuePrefix(typename) {
1379
- //TODO: handle other Edm types, enumeration types, and type definitions
1380
- if (['Edm.Int64', 'Edm.Int32', 'Edm.Int16', 'Edm.SByte', 'Edm.Byte',
1381
- 'Edm.Double', 'Edm.Single', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Guid'].includes(typename)) return '';
1382
- if (keyAsSegment) return '';
1383
- return `'`;
1384
- }
1385
-
1386
- /**
1387
- * Suffix for key value in key segment
1388
- * @param {typename} Qualified name of key property type
1389
- * @return {string} value prefix
1390
- */
1391
- function pathValueSuffix(typename) {
1392
- //TODO: handle other Edm types, enumeration types, and type definitions
1393
- if (['Edm.Int64', 'Edm.Int32', 'Edm.Int16', 'Edm.SByte', 'Edm.Byte',
1394
- 'Edm.Double', 'Edm.Single', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Guid'].includes(typename)) return '';
1395
- if (keyAsSegment) return '';
1396
- return `'`;
1397
- }
1398
-
1399
- /**
1400
- * Add path and Path Item Object for actions and functions bound to the element
1401
- * @param {object} paths Paths Object to augment
1402
- * @param {string} prefix Prefix for path
1403
- * @param {Array} prefixParameters Parameter Objects for prefix
1404
- * @param {object} element Model element the operations are bound to
1405
- * @param {string} sourceName Name of path source
1406
- * @param {boolean} byKey read by key
1407
- */
1408
- function pathItemsForBoundOperations(paths, prefix, prefixParameters, element, sourceName, byKey = false) {
1409
- const overloads = boundOverloads[element.$Type + (!byKey && element.$Collection ? '-c' : '')] || [];
1410
- overloads.forEach(item => {
1411
- if (item.overload.$Kind == 'Action')
1412
- pathItemAction(paths, prefix + '/' + item.name, prefixParameters, item.name, item.overload, sourceName);
1413
- else
1414
- pathItemFunction(paths, prefix + '/' + item.name, prefixParameters, item.name, item.overload, sourceName);
1415
- });
1416
- }
1417
-
1418
- /**
1419
- * Add path and Path Item Object for an action import
1420
- * @param {object} paths Paths Object to augment
1421
- * @param {string} name Name of action import
1422
- * @param {object} child Action import object
1423
- */
1424
- function pathItemActionImport(paths, name, child) {
1425
- const overload = modelElement(child.$Action).find(pOverload => !pOverload.$IsBound);
1426
- pathItemAction(paths, '/' + name, [], child.$Action, overload, child.$EntitySet, child);
1427
- }
1428
-
1429
- /**
1430
- * Add path and Path Item Object for action overload
1431
- * @param {object} paths Paths Object to augment
1432
- * @param {string} prefix Prefix for path
1433
- * @param {Array} prefixParameters Parameter Objects for prefix
1434
- * @param {string} actionName Qualified name of function
1435
- * @param {object} overload Function overload
1436
- * @param {string} sourceName Name of path source
1437
- * @param {string} actionImport Action import
1438
- */
1439
- function pathItemAction(paths, prefix, prefixParameters, actionName, overload, sourceName, actionImport = {}) {
1440
- const name = actionName.indexOf('.') === -1 ? actionName : nameParts(actionName).name;
1441
- const pathItem = {
1442
- post: {
1443
- summary: actionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes action ' + name,
1444
- tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
1445
- responses: overload.$ReturnType ? response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses)
1446
- : response(204, "Success", undefined, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
1447
- }
1448
- };
1449
- const description = actionImport[voc.Core.LongDescription] || overload[voc.Core.LongDescription];
1450
- if (description) pathItem.post.description = description;
1451
- if (prefixParameters.length > 0) pathItem.post.parameters = [...prefixParameters];
1452
- let parameters = overload.$Parameter || [];
1453
- if (overload.$IsBound) parameters = parameters.slice(1);
1454
- if (parameters.length > 0) {
1455
- const requestProperties = {};
1456
- parameters.forEach(p => { requestProperties[p.$Name] = getSchema(p) });
1457
- pathItem.post.requestBody = {
1458
- description: 'Action parameters',
1459
- content: {
1460
- 'application/json': {
1461
- schema: {
1462
- type: 'object',
1463
- properties: requestProperties
1464
- }
1465
- }
1466
- }
1467
- }
1468
- }
1469
- customParameters(pathItem.post, overload[voc.Capabilities.OperationRestrictions] || {});
1470
- paths[prefix] = pathItem;
1471
- }
1472
-
1473
- /**
1474
- * Add path and Path Item Object for an action import
1475
- * @param {object} paths Paths Object to augment
1476
- * @param {string} name Name of function import
1477
- * @param {object} child Function import object
1478
- */
1479
- function pathItemFunctionImport(paths, name, child) {
1480
- const overloads = modelElement(child.$Function);
1481
- console.assert(overloads, 'Unknown function "' + child.$Function + '" in function import "' + name + '"');
1482
- overloads && overloads.filter(overload => !overload.$IsBound).forEach(overload => pathItemFunction(paths, '/' + name, [], child.$Function, overload, child.$EntitySet, child));
1483
- }
1484
-
1485
- /**
1486
- * Add path and Path Item Object for function overload
1487
- * @param {object} paths Paths Object to augment
1488
- * @param {string} prefix Prefix for path
1489
- * @param {Array} prefixParameters Parameter Objects for prefix
1490
- * @param {string} functionName Qualified name of function
1491
- * @param {object} overload Function overload
1492
- * @param {string} sourceName Name of path source
1493
- * @param {object} functionImport Function Import
1494
- */
1495
- function pathItemFunction(paths, prefix, prefixParameters, functionName, overload, sourceName, functionImport = {}) {
1496
- const name = functionName.indexOf('.') === -1 ? functionName : nameParts(functionName).name;
1497
- let parameters = overload.$Parameter || [];
1498
- if (overload.$IsBound) parameters = parameters.slice(1);
1499
- const pathSegments = [];
1500
- const params = [];
1501
-
1502
- const implicitAliases = csdl.$Version > '4.0' || parameters.some(p => p[voc.Core.OptionalParameter]);
1503
-
1504
- parameters.forEach(p => {
1505
- const param = {
1506
- required: implicitAliases ? !p[voc.Core.OptionalParameter] : true
1507
- };
1508
- const description = [p[voc.Core.Description], p[voc.Core.LongDescription]].filter(t => t).join(' \n');
1509
- if (description) param.description = description;
1510
- const type = modelElement(p.$Type || 'Edm.String');
1511
- // TODO: check whether parameter or type definition of Edm.Stream is annotated with JSON.Schema
1512
- if (p.$Collection || p.$Type == 'Edm.Stream'
1513
- || type && ['ComplexType', 'EntityType'].includes(type.$Kind)
1514
- || type && type.$UnderlyingType == 'Edm.Stream') {
1515
- param.in = 'query';
1516
- if (implicitAliases) {
1517
- param.name = p.$Name;
1518
- } else {
1519
- pathSegments.push(p.$Name + '=@' + p.$Name);
1520
- param.name = '@' + p.$Name;
1521
- }
1522
- param.schema = { type: 'string' };
1523
- if (description) param.description += ' \n'; else param.description = '';
1524
- param.description += 'This is '
1525
- + (p.$Collection ? 'a ' : '')
1526
- + 'URL-encoded JSON '
1527
- + (p.$Collection ? 'array with items ' : '')
1528
- + 'of type '
1529
- + namespaceQualifiedName(p.$Type || 'Edm.String')
1530
- + ', see [Complex and Collection Literals](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_ComplexandCollectionLiterals)';
1531
- param.example = p.$Collection ? '[]' : '{}';
1532
- } else {
1533
- if (implicitAliases) {
1534
- param.in = 'query';
1535
- } else {
1536
- pathSegments.push(p.$Name + "={" + p.$Name + "}");
1537
- param.in = 'path';
1538
- }
1539
- param.name = p.$Name;
1540
- if (!p.$Type || p.$Type === "Edm.String" || (type && (!type.$Type || type.$Type === "Edm.String"))) {
1541
- if (description) param.description += " \n";
1542
- else param.description = "";
1543
- param.description += "String value needs to be enclosed in single quotes";
1544
- }
1545
- param.schema = getSchema(p, '', true, true);
1546
- }
1547
- params.push(param);
1548
- });
1549
-
1550
- const pathParameters = implicitAliases ? '' : '(' + pathSegments.join(',') + ')';
1551
- const pathItem = {
1552
- get: {
1553
- summary: functionImport[voc.Core.Description] || overload[voc.Core.Description] || 'Invokes function ' + name,
1554
- tags: [overload[voc.Common.Label] || sourceName || 'Service Operations'],
1555
- parameters: prefixParameters.concat(params),
1556
- responses: response(200, "Success", overload.$ReturnType, overload[voc.Capabilities.OperationRestrictions]?.ErrorResponses),
1557
- }
1558
- };
1559
- const iDescription = functionImport[voc.Core.LongDescription] || overload[voc.Core.LongDescription];
1560
- if (iDescription) pathItem.get.description = iDescription;
1561
- customParameters(pathItem.get, overload[voc.Capabilities.OperationRestrictions] || {});
1562
- paths[prefix + pathParameters] = pathItem;
1563
- }
1564
-
1565
- /**
1566
- * Add path and Path Item Object for batch requests
1567
- * @param {object} paths Paths Object to augment
1568
- * @param {object} container Entity container
1569
- */
1570
- function pathItemBatch(paths, container) {
1571
- const batchSupport = container[voc.Capabilities.BatchSupport] || {};
1572
- const supported = container[voc.Capabilities.BatchSupported] !== false && batchSupport.Supported !== false;
1573
- if (supported) {
1574
- const firstEntitySet = Object.keys(container).filter(child => isIdentifier(child) && container[child].$Collection)[0];
1575
- paths['/$batch'] = {
1576
- post: {
1577
- summary: batchSupport[voc.Core.Description] || 'Sends a group of requests',
1578
- description: (batchSupport[voc.Core.LongDescription] || 'Group multiple requests into a single request payload, see '
1579
- + '[Batch Requests](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_BatchRequests).')
1580
- + '\n\n*Please note that "Try it out" is not supported for this request.*',
1581
- tags: ['Batch Requests'],
1582
- requestBody: {
1583
- required: true,
1584
- description: 'Batch request',
1585
- content: {
1586
- 'multipart/mixed;boundary=request-separator': {
1587
- schema: {
1588
- type: 'string'
1589
- },
1590
- example: '--request-separator\n'
1591
- + 'Content-Type: application/http\n'
1592
- + 'Content-Transfer-Encoding: binary\n\n'
1593
- + 'GET ' + firstEntitySet + ' HTTP/1.1\n'
1594
- + 'Accept: application/json\n\n'
1595
- + '\n--request-separator--'
1596
- }
1597
- }
1598
- },
1599
- responses: {
1600
- '4XX': {
1601
- $ref: '#/components/responses/error'
1602
- }
1603
- }
1604
- }
1605
- };
1606
- paths['/$batch'].post.responses[csdl.$Version < '4.0' ? 202 : 200] = {
1607
- description: 'Batch response',
1608
- content: {
1609
- 'multipart/mixed': {
1610
- schema: {
1611
- type: 'string'
1612
- },
1613
- example: '--response-separator\n'
1614
- + 'Content-Type: application/http\n\n'
1615
- + 'HTTP/1.1 200 OK\n'
1616
- + 'Content-Type: application/json\n\n'
1617
- + '{...}'
1618
- + '\n--response-separator--'
1619
- }
1620
- }
1621
- };
1622
- }
1623
- }
1624
-
1625
- /**
1626
- * Construct Responses Object
1627
- * @param {string} code HTTP response code
1628
- * @param {string} description Description
1629
- * @param {object} type Response type object
1630
- * @param {array} errors Array of operation-specific status codes with descriptions
1631
- */
1632
- function response(code, description, type, errors, isCount = true) {
1633
- const r = {};
1634
- r[code] = {
1635
- description: description
1636
- };
1637
- let CountPropertyObj = { [csdl.$Version > '4.0' ? '@count' : '@odata.count']: ref('count') };
1638
- if (code != 204) {
1639
- const s = getSchema(type);
1640
- r[code].content = {
1641
- 'application/json': {
1642
- schema: type.$Collection
1643
- ? {
1644
- type: 'object',
1645
- title: 'Collection of ' + nameParts(type.$Type || 'Edm.String').name,
1646
- properties: {
1647
- ...(isCount && CountPropertyObj),
1648
- value: s
1649
- }
1650
- }
1651
- : s
1652
- }
1653
- };
1654
- }
1655
- if (errors) {
1656
- for (const e of errors) {
1657
- r[e.StatusCode] = {
1658
- description: e.Description,
1659
- content: {
1660
- "application/json": {
1661
- schema: { $ref: "#/components/schemas/error" },
1662
- },
1663
- },
1664
- };
1665
- }
1666
- } else {
1667
- r["4XX"] = {
1668
- $ref: "#/components/responses/error",
1669
- };
1670
- }
1671
- return r;
1672
- }
1673
-
1674
- /**
1675
- * Construct the Components Object from the types of the CSDL document
1676
- * @param {object} csdl CSDL document
1677
- * @param {object} entityContainer Entity Container object
1678
- * @return {object} Components Object
1679
- */
1680
- function getComponents(csdl, entityContainer) {
1681
- const c = {
1682
- schemas: getSchemas(csdl)
1683
- };
1684
-
1685
- if (csdl.$EntityContainer) {
1686
- c.parameters = getParameters();
1687
- c.responses = {
1688
- error: {
1689
- description: 'Error',
1690
- content: {
1691
- 'application/json': {
1692
- schema: ref('error')
1693
- }
1694
- }
1695
- }
1696
- };
1697
- }
1698
-
1699
- getSecuritySchemes(c, entityContainer)
1700
-
1701
- return c;
1702
- }
1703
-
1704
- /**
1705
- * Construct Schema Objects from the types of the CSDL document
1706
- * @param {object} csdl CSDL document
1707
- * @return {object} Map of Schema Objects
1708
- */
1709
- function getSchemas(csdl) {
1710
- const unordered = {};
1711
-
1712
- for (const r of requiredSchemas.list) {
1713
- const type = modelElement(`${r.namespace}.${r.name}`);
1714
- if (!type) continue;
1715
- switch (type.$Kind) {
1716
- case "ComplexType":
1717
- case "EntityType":
1718
- schemasForStructuredType(unordered, r.namespace, r.name, type, r.suffix);
1719
- break;
1720
- case "EnumType":
1721
- schemaForEnumerationType(unordered, r.namespace, r.name, type);
1722
- break;
1723
- case "TypeDefinition":
1724
- schemaForTypeDefinition(unordered, r.namespace, r.name, type);
1725
- break;
1726
- }
1727
- }
1728
- const ordered = {};
1729
- for (const name of Object.keys(unordered).sort()) {
1730
- ordered[name] = unordered[name];
1731
- }
1732
-
1733
- inlineTypes(ordered);
1734
-
1735
- if (csdl.$EntityContainer) {
1736
- ordered.count = count();
1737
- ordered.error = error();
1738
- }
1739
-
1740
- return ordered;
1741
- }
1742
-
1743
- /**
1744
- * Construct Schema Objects from the types of the CSDL document
1745
- * @param {object} schemas Map of Schema Objects to augment
1746
- */
1747
- function inlineTypes(schemas) {
1748
- if (typesToInline.geoPoint) {
1749
- schemas.geoPoint = {
1750
- type: 'object',
1751
- properties: {
1752
- coordinates: ref('geoPosition'),
1753
- type: {
1754
- type: 'string',
1755
- enum: ['Point'],
1756
- default: 'Point'
1757
- }
1758
- },
1759
- required: ['type', 'coordinates']
1760
- };
1761
- schemas.geoPosition = {
1762
- type: 'array',
1763
- minItems: 2,
1764
- items: {
1765
- type: 'number'
1766
- }
1767
- }
1768
- }
1769
- }
1770
-
1771
- /**
1772
- * Construct Schema Objects for an enumeration type
1773
- * @param {object} schemas Map of Schema Objects to augment
1774
- * @param {string} qualifier Qualifier for structured type
1775
- * @param {string} name Simple name of structured type
1776
- * @param {object} type Structured type
1777
- * @return {object} Map of Schemas Objects
1778
- */
1779
- function schemaForEnumerationType(schemas, qualifier, name, type) {
1780
- const members = [];
1781
- Object.keys(type).filter(iName => isIdentifier(iName)).forEach(iName2 => {
1782
- members.push(iName2);
1783
- });
1784
-
1785
- const s = {
1786
- type: 'string',
1787
- title: name,
1788
- enum: members
1789
- };
1790
- const description = type[voc.Core.LongDescription];
1791
- if (description) s.description = description;
1792
- schemas[qualifier + '.' + name] = s;
1793
- }
1794
-
1795
- /**
1796
- * Construct Schema Objects for a type definition
1797
- * @param {object} schemas Map of Schema Objects to augment
1798
- * @param {string} qualifier Qualifier for structured type
1799
- * @param {string} name Simple name of structured type
1800
- * @param {object} type Structured type
1801
- * @return {object} Map of Schemas Objects
1802
- */
1803
- function schemaForTypeDefinition(schemas, qualifier, name, type) {
1804
- const s = getSchema(Object.assign({ $Type: type.$UnderlyingType }, type));
1805
- s.title = name;
1806
- const description = type[voc.Core.LongDescription];
1807
- if (description) s.description = description;
1808
- schemas[qualifier + '.' + name] = s;
1809
- }
1810
-
1811
- /**
1812
- * Construct Schema Objects for a structured type
1813
- * @param {object} schemas Map of Schema Objects to augment
1814
- * @param {string} qualifier Qualifier for structured type
1815
- * @param {string} name Simple name of structured type
1816
- * @param {string} suffix Suffix for read/create/update
1817
- * @param {object} type Structured type
1818
- * @return {object} Map of Schemas Objects
1819
- */
1820
- function schemasForStructuredType(schemas, qualifier, name, type, suffix) {
1821
- const schemaName = qualifier + "." + name + suffix;
1822
- const baseName = qualifier + "." + name;
1823
- const isKey = keyMap(type);
1824
- const required = Object.keys(isKey);
1825
- const schemaProperties = {};
1826
- let isCount = true;
1827
- if (csdl[qualifier]?.$Annotations) {
1828
- const annotations = csdl[qualifier].$Annotations[`${qualifier}.EntityContainer/${name}`];
1829
- if (annotations && annotations[voc.Capabilities.CountRestrictions] && annotations[voc.Capabilities.CountRestrictions]?.Countable === false) {
1830
- isCount = false;
1831
- }
1832
- }
1833
- const properties = propertiesOfStructuredType(type);
1834
- Object.keys(properties).forEach(iName => {
1835
- const property = properties[iName];
1836
- if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property);
1837
- if ((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory') { required.push(iName) }
1838
- if (property.$Kind == 'NavigationProperty') {
1839
- if (property.$Collection && suffix === "" && isCount === true) {
1840
- schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count');
1841
- }
1842
- if (property[voc.Core.Permissions] != "Read" && !property[voc.Core.Computed] && ( property.$ContainsTarget || property.$OnDelete === 'Cascade') ) {
1843
- if (suffix === SUFFIX.create)
1844
- schemaProperties[iName] = getSchema(property, SUFFIX.create);
1845
- if (suffix === SUFFIX.update)
1846
- schemaProperties[iName] = getSchema(property, SUFFIX.create);
1847
- }
1848
- } else {
1849
- if (property[voc.Core.Permissions] === "Read" || property[voc.Core.Computed]) {
1850
- let index = required.indexOf(iName);
1851
- if (index != -1) required.splice(index, 1);
1852
- } else {
1853
- if (suffix === SUFFIX.create)
1854
- schemaProperties[iName] = getSchema(property, SUFFIX.create);
1855
- if (suffix === SUFFIX.update && !isKey[iName] && !property[voc.Core.Immutable])
1856
- schemaProperties[iName] = getSchema(property, SUFFIX.update);
1857
- }
1858
- }
1859
- });
1860
-
1861
-
1862
- schemas[schemaName] = {
1863
- title: (type[voc.Core.Description] || name) + TITLE_SUFFIX[suffix],
1864
- type: 'object'
1865
- };
1866
- if (Object.keys(schemaProperties).length > 0)
1867
- schemas[schemaName].properties = schemaProperties;
1868
-
1869
- if (suffix === SUFFIX.read && type["@ODM.root"]) schemas[schemaName]["x-sap-root-entity"] = type["@ODM.root"]
1870
- odmExtensions(type, schemas[schemaName]);
1871
-
1872
- if (suffix === SUFFIX.create && required.length > 0)
1873
- schemas[schemaName].required = [...new Set(required)];
1874
-
1875
- const description = type[voc.Core.LongDescription];
1876
- if (description) {
1877
- schemas[schemaName].description = description;
1878
- }
1879
-
1880
- if (derivedTypes[baseName]) {
1881
- schemas[schemaName].anyOf = [];
1882
- derivedTypes[baseName].forEach((derivedType) => {
1883
- schemas[schemaName].anyOf.push(ref(derivedType, suffix));
1884
- });
1885
- if (!type.$Abstract) schemas[schemaName].anyOf.push({});
1886
- }
1887
- }
1888
-
1889
- /**
1890
- * Add ODM extensions to OpenAPI schema for a structured type
1891
- * @param {object} type Structured type
1892
- * @param {object} schema OpenAPI schema to augment
1893
- */
1894
- function odmExtensions(type, schema) {
1895
- for (const [annotation, openApiExtension] of Object.entries(ODM_ANNOTATIONS)) {
1896
- if (type[annotation]) schema[openApiExtension] = type[annotation];
1897
- }
1898
- }
1899
-
1900
- /**
1901
- * Collect all properties of a structured type along the inheritance hierarchy
1902
- * @param {object} type Structured type
1903
- * @return {object} Map of properties
1904
- */
1905
- function propertiesOfStructuredType(type) {
1906
- const properties = (type && type.$BaseType) ? propertiesOfStructuredType(modelElement(type.$BaseType)) : {};
1907
- if (type) {
1908
- Object.keys(type).filter(name => isIdentifier(name)).forEach(name => {
1909
- properties[name] = type[name];
1910
- });
1911
- }
1912
- return properties;
1913
- }
1914
-
1915
- /**
1916
- * Construct Parameter Objects for type-independent OData system query options
1917
- * @return {object} Map of Parameter Objects
1918
- */
1919
- function getParameters() {
1920
- const param = {
1921
- top: {
1922
- name: queryOptionPrefix + 'top',
1923
- in: 'query',
1924
- description: 'Show only the first n items, see [Paging - Top](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptiontop)',
1925
- schema: {
1926
- type: 'integer',
1927
- minimum: 0
1928
- },
1929
- example: 50
1930
- },
1931
- skip: {
1932
- name: queryOptionPrefix + 'skip',
1933
- in: 'query',
1934
- description: 'Skip the first n items, see [Paging - Skip](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionskip)',
1935
- schema: {
1936
- type: 'integer',
1937
- minimum: 0
1938
- }
1939
- },
1940
- count: {
1941
- name: queryOptionPrefix + 'count',
1942
- in: 'query',
1943
- description: 'Include count of items, see [Count](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptioncount)',
1944
- schema: {
1945
- type: 'boolean'
1946
- }
1947
- }
1948
- };
1949
-
1950
- if (csdl.$Version >= '4.0') param.search = {
1951
- name: queryOptionPrefix + 'search',
1952
- in: 'query',
1953
- description: 'Search items by search phrases, see [Searching](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionsearch)',
1954
- schema: {
1955
- type: 'string'
1956
- }
1957
- };
1958
-
1959
- return param;
1960
- }
1961
-
1962
- /**
1963
- * Construct OData error response
1964
- * @return {object} Error response schema
1965
- */
1966
- function error() {
1967
- const err = {
1968
- type: 'object',
1969
- required: ['error'],
1970
- properties: {
1971
- error: {
1972
- type: 'object',
1973
- required: ['code', 'message'],
1974
- properties: {
1975
- code: { type: 'string' },
1976
- message: { type: 'string' },
1977
- target: { type: 'string' },
1978
- details: {
1979
- type: 'array',
1980
- items: {
1981
- type: 'object',
1982
- required: ['code', 'message'],
1983
- properties: {
1984
- code: { type: 'string' },
1985
- message: { type: 'string' },
1986
- target: { type: 'string' }
1987
- }
1988
- }
1989
- },
1990
- innererror: {
1991
- type: 'object',
1992
- description: 'The structure of this object is service-specific'
1993
- }
1994
- }
1995
- }
1996
- }
1997
- };
1998
-
1999
- if (csdl.$Version < '4.0') {
2000
- err.properties.error.properties.message = {
2001
- type: 'object',
2002
- properties: {
2003
- lang: { type: 'string' },
2004
- value: { type: 'string' }
2005
- },
2006
- required: ['lang', 'value']
2007
- };
2008
- delete err.properties.error.properties.details;
2009
- delete err.properties.error.properties.target;
2010
- }
2011
-
2012
- return err;
2013
- }
2014
-
2015
- /**
2016
- * Construct OData count response
2017
- * @return {object} Count response schema
2018
- */
2019
- function count() {
2020
- return {
2021
- anyOf: [
2022
- { type: 'number' },
2023
- { type: 'string' }
2024
- ],
2025
- description: 'The number of entities in the collection. Available when using the [$count](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptioncount) query option.',
2026
- };
2027
- }
2028
-
2029
- /**
2030
- * Construct Schema Object for model object referencing a type
2031
- * @param {object} modelElement referencing a type
2032
- * @return {object} Schema Object
2033
- */
2034
- function getSchema(element, suffix = '', forParameter = false, forFunction = false) {
2035
- let s = {};
2036
- switch (element.$Type) {
2037
- case 'Edm.AnnotationPath':
2038
- case 'Edm.ModelElementPath':
2039
- case 'Edm.NavigationPropertyPath':
2040
- case 'Edm.PropertyPath':
2041
- s.type = 'string';
2042
- break;
2043
- case 'Edm.Binary':
2044
- s = {
2045
- type: 'string',
2046
- format: 'base64url'
2047
- };
2048
- if (element.$MaxLength) s.maxLength = Math.ceil(4 * element.$MaxLength / 3);
2049
- break;
2050
- case 'Edm.Boolean':
2051
- s.type = 'boolean';
2052
- break;
2053
- case 'Edm.Byte':
2054
- s = {
2055
- type: 'integer',
2056
- format: 'uint8'
2057
- };
2058
- break;
2059
- case 'Edm.Date':
2060
- s = {
2061
- type: 'string',
2062
- format: 'date',
2063
- example: '2017-04-13'
2064
- };
2065
- break;
2066
- case 'Edm.DateTime':
2067
- case 'Edm.DateTimeOffset':
2068
- s = {
2069
- type: 'string',
2070
- format: 'date-time',
2071
- example: '2017-04-13T15:51:04' + (isNaN(element.$Precision) || element.$Precision === 0 ? '' : '.' + '0'.repeat(element.$Precision)) + 'Z'
2072
- };
2073
- break;
2074
- case 'Edm.Decimal':
2075
- s = {
2076
- anyOf: [{ type: 'number', format: 'decimal' }, { type: 'string' }],
2077
- example: 0
2078
- };
2079
- if (!isNaN(element.$Precision)) s['x-sap-precision'] = element.$Precision;
2080
- if (!isNaN(element.$Scale)) s['x-sap-scale'] = element.$Scale;
2081
- // eslint-disable-next-line no-case-declarations
2082
- let scale = !isNaN(element.$Scale) ? element.$Scale : null;
2083
- if (scale !== null) {
2084
- // Node.js 12.13.0 has problems with negative exponents, 10 ** -5 --> 0.000009999999999999999
2085
- if (scale <= 0)
2086
- s.anyOf[0].multipleOf = 10 ** -scale;
2087
- else
2088
- s.anyOf[0].multipleOf = 1 / 10 ** scale;
2089
- }
2090
- if (element.$Precision < 16) {
2091
- let limit = 10 ** (element.$Precision - scale);
2092
- let delta = 10 ** -scale;
2093
- s.anyOf[0].maximum = limit - delta;
2094
- s.anyOf[0].minimum = -s.anyOf[0].maximum;
2095
- }
2096
- break;
2097
- case 'Edm.Double':
2098
- s = {
2099
- anyOf: [{ type: 'number', format: 'double' }, { type: 'string' }],
2100
- example: 3.14
2101
- };
2102
- break;
2103
- case 'Edm.Duration':
2104
- s = {
2105
- type: 'string',
2106
- format: 'duration',
2107
- example: 'P4DT15H51M04S'
2108
- };
2109
- break;
2110
- case 'Edm.GeographyPoint':
2111
- case 'Edm.GeometryPoint':
2112
- s = ref('geoPoint');
2113
- typesToInline.geoPoint = true;
2114
- break;
2115
- case 'Edm.Guid':
2116
- s = {
2117
- type: 'string',
2118
- format: 'uuid',
2119
- example: '01234567-89ab-cdef-0123-456789abcdef'
2120
- };
2121
- break;
2122
- case 'Edm.Int16':
2123
- s = {
2124
- type: 'integer',
2125
- format: 'int16'
2126
- };
2127
- break;
2128
- case 'Edm.Int32':
2129
- s = {
2130
- type: 'integer',
2131
- format: 'int32'
2132
- };
2133
- break;
2134
- case 'Edm.Int64':
2135
- s = {
2136
- anyOf: [{ type: 'integer', format: 'int64' }, { type: 'string' }],
2137
- example: "42"
2138
- };
2139
- break;
2140
- case 'Edm.PrimitiveType':
2141
- s = {
2142
- anyOf: [{ type: 'boolean' }, { type: 'number' }, { type: 'string' }]
2143
- };
2144
- break;
2145
- case 'Edm.SByte':
2146
- s = {
2147
- type: 'integer',
2148
- format: 'int8'
2149
- };
2150
- break;
2151
- case 'Edm.Single':
2152
- s = {
2153
- anyOf: [{ type: 'number', format: 'float' }, { type: 'string' }],
2154
- example: 3.14
2155
- };
2156
- break;
2157
- case 'Edm.Stream':
2158
- // eslint-disable-next-line no-case-declarations
2159
- let jsonSchema = element[voc.JSON.Schema];
2160
- if (jsonSchema) {
2161
- if (typeof jsonSchema == 'string')
2162
- s = JSON.parse(jsonSchema);
2163
- else
2164
- s = jsonSchema;
2165
- } else {
2166
- s = {
2167
- type: 'string',
2168
- format: 'base64url'
2169
- };
2170
- }
2171
- break;
2172
- case 'Edm.String':
2173
- case undefined:
2174
- s.type = 'string';
2175
- if (element.$MaxLength) s.maxLength = element.$MaxLength;
2176
- allowedValues(s, element);
2177
- getPattern(s, element);
2178
- break;
2179
- case 'Edm.TimeOfDay':
2180
- s = {
2181
- type: 'string',
2182
- format: 'time',
2183
- example: '15:51:04'
2184
- };
2185
- break;
2186
- default:
2187
- if (element.$Type.startsWith('Edm.')) {
2188
- console.warn('Unknown type: ' + element.$Type);
2189
- } else {
2190
- let type = modelElement(element.$Type);
2191
- let isStructured = type && ['ComplexType', 'EntityType'].includes(type.$Kind);
2192
- s = ref(element.$Type, (isStructured ? suffix : ''));
2193
- if (element.$MaxLength) {
2194
- s = {
2195
- allOf: [s],
2196
- maxLength: element.$MaxLength
2197
- };
2198
- }
2199
- }
2200
- }
2201
-
2202
- if (element.$Nullable) {
2203
- if (s.$ref) s = { allOf: [s] };
2204
- s.nullable = true;
2205
- }
2206
-
2207
- if (element.$DefaultValue !== undefined) {
2208
- if (s.$ref) s = { allOf: [s] };
2209
- s.default = element.$DefaultValue;
2210
- }
2211
-
2212
- if (element[voc.Core.Example]) {
2213
- if (s.$ref) s = { allOf: [s] };
2214
- s.example = element[voc.Core.Example].Value;
2215
- }
2216
-
2217
- if (forFunction) {
2218
- if (s.example && typeof s.example === "string") {
2219
- s.example = `${pathValuePrefix(element.$Type)}${s.example
2220
- }${pathValueSuffix(element.$Type)} `;
2221
- }
2222
- if (s.pattern) {
2223
- const pre = pathValuePrefix(element.$Type);
2224
- const suf = pathValueSuffix(element.$Type);
2225
- s.pattern = s.pattern.replace(/^\^/, `^ ${pre} (`);
2226
- s.pattern = s.pattern.replace(/\$$/, `)${suf} $`);
2227
- } else if (!element.$Type || element.$Type === "Edm.String") {
2228
- s.pattern = "^'([^']|'')*'$";
2229
- }
2230
- if (element.$Nullable) {
2231
- if (!s.anyOf && !s.allOf) s.type = "string";
2232
- if (s.format) s.format += ",null";
2233
- if (s.anyOf && s.anyOf[0].format) s.anyOf[0].format += ",null";
2234
- s.default = "null";
2235
- if (s.pattern) {
2236
- s.pattern = s.pattern.replace(/^\^/, "^(null|");
2237
- s.pattern = s.pattern.replace(/\$$/, ")$");
2238
- }
2239
- }
2240
- }
2241
-
2242
- if (element[voc.Validation.Maximum] != undefined) {
2243
- if (s.$ref) s = { allOf: [s] };
2244
- if (s.anyOf) {
2245
- s.anyOf[0].maximum = element[voc.Validation.Maximum];
2246
- }
2247
- if (element[voc.Validation.Maximum + voc.Validation.Exclusive]) s.exclusiveMaximum = true;
2248
- }
2249
-
2250
- if (element[voc.Validation.Minimum] != undefined) {
2251
- if (s.$ref) s = { allOf: [s] };
2252
- if (s.anyOf) {
2253
- s.anyOf[0].minimum = element[voc.Validation.Minimum];
2254
- }
2255
- if (element[voc.Validation.Minimum + voc.Validation.Exclusive]) s.exclusiveMinimum = true;
2256
- }
2257
-
2258
- if (element.$Collection) {
2259
- s = {
2260
- type: 'array',
2261
- items: s
2262
- };
2263
- }
2264
-
2265
- if (!forParameter && element[voc.Core.LongDescription]) {
2266
- if (s.$ref) s = { allOf: [s] };
2267
- s.description = element[voc.Core.LongDescription];
2268
- }
2269
-
2270
- if (element['@ODM.oidReference']?.entityName) {
2271
- s['x-sap-odm-oid-reference-entity-name'] = element['@ODM.oidReference'].entityName
2272
- }
2273
-
2274
- return s;
2275
- }
2276
-
2277
- /**
2278
- * Add allowed values enum to Schema Object for string-like model element
2279
- * @param {object} schema Schema Object to augment
2280
- * @param {object} element Model element
2281
- */
2282
- function allowedValues(schema, element) {
2283
- const values = element[voc.Validation.AllowedValues];
2284
- if (values) schema.enum = values.map(record => record.Value);
2285
- }
2286
-
2287
- /**
2288
- * Add pattern to Schema Object for string-like model element
2289
- * @param {object} schema Schema Object to augment
2290
- * @param {object} element Model element
2291
- */
2292
- function getPattern(schema, element) {
2293
- const pattern = element[voc.Validation.Pattern];
2294
- if (pattern) schema.pattern = pattern;
2295
- }
2296
-
2297
- /**
2298
- * Construct Reference Object for a type
2299
- * @param {string} typename Qualified name of referenced type
2300
- * @param {string} suffix Optional suffix for referenced schema
2301
- * @return {object} Reference Object
2302
- */
2303
- function ref(typename, suffix = '') {
2304
- let name = typename;
2305
- let nsp = '';
2306
- let url = '';
2307
- if (typename.indexOf('.') != -1) {
2308
- let parts = nameParts(typename);
2309
- nsp = namespace[parts.qualifier];
2310
- name = nsp + '.' + parts.name;
2311
- url = namespaceUrl[nsp] || '';
2312
- if (url === "" && !requiredSchemas.used[name + suffix]) {
2313
- requiredSchemas.used[name + suffix] = true;
2314
- requiredSchemas.list.push({ namespace: nsp, name: parts.name, suffix });
2315
- }
2316
- //TODO: introduce better way than guessing
2317
- if (url.endsWith('.xml')) url = url.substring(0, url.length - 3) + "openapi3.json";
2318
- }
2319
- return {
2320
- $ref: url + '#/components/schemas/' + name + suffix
2321
- };
2322
- }
2323
-
2324
- /**
2325
- * Augment Components Object with map of Security Scheme Objects
2326
- * @param {object} components Components Object to augment
2327
- * @param {object} entityContainer Entity Container object
2328
- */
2329
- function getSecuritySchemes(components, entityContainer) {
2330
- const authorizations = entityContainer && entityContainer[voc.Authorization.Authorizations] ? entityContainer[voc.Authorization.Authorizations] : [];
2331
- const schemes = {};
2332
- const location = { Header: 'header', QueryOption: 'query', Cookie: 'cookie' };
2333
- authorizations.forEach(auth => {
2334
- const scheme = {};
2335
- const flow = {};
2336
- if (auth.Description) scheme.description = auth.Description;
2337
- const qualifiedType = auth['@type'] || auth['@odata.type']
2338
- const type = qualifiedType.substring(qualifiedType.lastIndexOf(".") + 1);
2339
- let unknown = false
2340
- switch (type) {
2341
- case 'ApiKey':
2342
- scheme.type = 'apiKey';
2343
- scheme.name = auth.KeyName;
2344
- scheme.in = location[auth.Location];
2345
- break;
2346
- case 'Http':
2347
- scheme.type = 'http';
2348
- scheme.scheme = auth.Scheme;
2349
- scheme.bearerFormat = auth.BearerFormat;
2350
- break;
2351
- case 'OAuth2AuthCode':
2352
- scheme.type = 'oauth2';
2353
- scheme.flows = { authorizationCode: flow };
2354
- flow.authorizationUrl = auth.AuthorizationUrl;
2355
- flow.tokenUrl = auth.TokenUrl;
2356
- if (auth.RefreshUrl) flow.refreshUrl = auth.RefreshUrl;
2357
- flow.scopes = getScopes(auth);
2358
- break;
2359
- case 'OAuth2ClientCredentials':
2360
- scheme.type = 'oauth2';
2361
- scheme.flows = { clientCredentials: flow };
2362
- flow.tokenUrl = auth.TokenUrl;
2363
- if (auth.RefreshUrl) flow.refreshUrl = auth.RefreshUrl;
2364
- flow.scopes = getScopes(auth);
2365
- break;
2366
- case 'OAuth2Implicit':
2367
- scheme.type = 'oauth2';
2368
- scheme.flows = { implicit: flow };
2369
- flow.authorizationUrl = auth.AuthorizationUrl;
2370
- if (auth.RefreshUrl) flow.refreshUrl = auth.RefreshUrl;
2371
- flow.scopes = getScopes(auth);
2372
- break;
2373
- case 'OAuth2Password':
2374
- scheme.type = 'oauth2';
2375
- scheme.flows = {};
2376
- scheme.flows = { password: flow };
2377
- flow.tokenUrl = auth.TokenUrl;
2378
- if (auth.RefreshUrl) flow.refreshUrl = auth.RefreshUrl;
2379
- flow.scopes = getScopes(auth);
2380
- break;
2381
- case 'OpenIDConnect':
2382
- scheme.type = 'openIdConnect';
2383
- scheme.openIdConnectUrl = auth.IssuerUrl;
2384
- break;
2385
- default:
2386
- unknown = true
2387
- console.warn('Unknown Authorization type ' + qualifiedType);
2388
- }
2389
- if (!unknown) schemes[auth.Name] = scheme;
2390
- });
2391
- if (Object.keys(schemes).length > 0) components.securitySchemes = schemes
2392
- }
2393
-
2394
- function getScopes(authorization) {
2395
- const scopes = {};
2396
- authorization.Scopes.forEach(scope => { scopes[scope.Scope] = scope.Description });
2397
- return scopes;
2398
- }
2399
-
2400
- /**
2401
- * Augment OpenAPI document with Security Requirements Object
2402
- * @param {object} openapi OpenAPI document to augment
2403
- * @param {object} entityContainer Entity Container object
2404
- */
2405
- function security(openapi, entityContainer) {
2406
- const securitySchemes = entityContainer && entityContainer[voc.Authorization.SecuritySchemes] ? entityContainer[voc.Authorization.SecuritySchemes] : [];
2407
- if (securitySchemes.length > 0) openapi.security = [];
2408
- securitySchemes.forEach(scheme => {
2409
- const s = {};
2410
- s[scheme.Authorization] = scheme.RequiredScopes || [];
2411
- openapi.security.push(s);
2412
- });
2413
- }
2414
-
2415
- /**
2416
- * a qualified name consists of a namespace or alias, a dot, and a simple name
2417
- * @param {string} qualifiedName
2418
- * @return {string} namespace-qualified name
2419
- */
2420
- function namespaceQualifiedName(qualifiedName) {
2421
- let np = nameParts(qualifiedName);
2422
- return namespace[np.qualifier] + '.' + np.name;
2423
- }
2424
-
2425
- /**
2426
- * a qualified name consists of a namespace or alias, a dot, and a simple name
2427
- * @param {string} qualifiedName
2428
- * @return {object} with components qualifier and name
2429
- */
2430
- function nameParts(qualifiedName) {
2431
- const pos = qualifiedName.lastIndexOf('.');
2432
- console.assert(pos > 0, 'Invalid qualified name ' + qualifiedName);
2433
- return {
2434
- qualifier: qualifiedName.substring(0, pos),
2435
- name: qualifiedName.substring(pos + 1)
2436
- };
2437
- }
2438
-
2439
- /**
2440
- * an identifier does not start with $ and does not contain @
2441
- * @param {string} name
2442
- * @return {boolean} name is an identifier
2443
- */
2444
- function isIdentifier(name) {
2445
- return !name.startsWith('$') && !name.includes('@');
2446
- }
2447
-
2448
- };