@sap/cds-dk 8.2.3 → 8.4.0

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 (84) hide show
  1. package/CHANGELOG.md +66 -11
  2. package/bin/build.js +1 -1
  3. package/bin/cds.js +17 -4
  4. package/bin/compile/index.js +12 -3
  5. package/bin/deploy/index.js +22 -3
  6. package/bin/env.js +3 -2
  7. package/bin/import.js +24 -14
  8. package/bin/init.js +8 -4
  9. package/bin/repl.js +21 -8
  10. package/bin/util/fuzzySearch.js +8 -5
  11. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
  12. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +1 -1
  13. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +1 -1
  14. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
  15. package/lib/build/plugins/helm/files/subcharts/web-application/templates/virtual-service.yaml +23 -12
  16. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +1 -1
  17. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +2 -2
  18. package/lib/build/util.js +32 -20
  19. package/lib/compile/to-mermaid.js +87 -0
  20. package/lib/deploy/to-hana/hana.js +2 -21
  21. package/lib/env/schemas/cds-rc.js +174 -2
  22. package/lib/import/asyncapi/parserContext.js +4 -30
  23. package/lib/import/common.js +2 -2
  24. package/lib/import/importapi.js +3 -3
  25. package/lib/import/odata/v2parser.js +3 -2
  26. package/lib/import/openapi/importOpenAPI.js +622 -537
  27. package/lib/import/openapi/utilities.js +7 -2
  28. package/lib/init/constants.js +2 -1
  29. package/lib/init/index.js +4 -0
  30. package/lib/init/merge.js +2 -2
  31. package/lib/init/mvn.js +2 -2
  32. package/lib/init/projectReader.js +2 -1
  33. package/lib/init/registries/mta.js +8 -1
  34. package/lib/init/registries/xs-app.js +10 -0
  35. package/lib/init/template/approuter/files/package.json +1 -1
  36. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +3 -1
  37. package/lib/init/template/cf-manifest/index.js +2 -0
  38. package/lib/init/template/cloud-logging/files/mta.yaml.hbs +13 -2
  39. package/lib/init/template/cloud-logging/files/{package.json → package.json.hbs} +5 -1
  40. package/lib/init/template/cloud-logging/index.js +4 -3
  41. package/lib/init/template/data/as-csv.js +6 -0
  42. package/lib/init/template/data/as-json.js +8 -1
  43. package/lib/init/template/data/index.js +1 -1
  44. package/lib/init/template/destination/files/mta.yaml.hbs +5 -5
  45. package/lib/init/template/dynatrace/files/mta.yaml.hbs +22 -6
  46. package/lib/init/template/dynatrace/files/package.json.hbs +5 -2
  47. package/lib/init/template/dynatrace/files/values.yaml +15 -0
  48. package/lib/init/template/dynatrace/index.js +10 -5
  49. package/lib/init/template/enterprise-messaging-shared/index.js +1 -1
  50. package/lib/init/template/file-based-messaging/index.js +1 -1
  51. package/lib/init/template/hana/files/package.json.hbs +2 -0
  52. package/lib/init/template/hana/index.js +1 -1
  53. package/lib/init/template/handler/index.js +16 -1
  54. package/lib/init/template/helm/files/values.yaml.hbs +2 -2
  55. package/lib/init/template/helm/index.js +6 -1
  56. package/lib/init/template/html5-repo/files/app-package.json.hbs +3 -1
  57. package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +7 -0
  58. package/lib/init/template/html5-repo/index.js +3 -1
  59. package/lib/init/template/local-messaging/index.js +1 -1
  60. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  61. package/lib/init/template/mta/index.js +2 -0
  62. package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -5
  63. package/lib/init/template/nodejs/files/.vscode/settings.json +1 -1
  64. package/lib/init/template/nodejs/files/.vscode/tasks.json +0 -2
  65. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  66. package/lib/init/template/postgres/index.js +1 -1
  67. package/lib/init/template/redis-messaging/index.js +1 -1
  68. package/lib/init/template/side-by-side-extensibility/files/mta.yaml.hbs +40 -0
  69. package/lib/init/template/side-by-side-extensibility/files/package.json.hbs +17 -0
  70. package/lib/init/template/side-by-side-extensibility/index.js +34 -0
  71. package/lib/init/template/typer/index.js +2 -1
  72. package/lib/init/template/typescript/files/package.json +2 -2
  73. package/lib/init/template/workzone-standard/files/manifest.json.hbs +15 -1
  74. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +11 -2
  75. package/lib/init/template/workzone-standard/files/xs-app.json.hbs +18 -0
  76. package/lib/init/template/workzone-standard/index.js +25 -7
  77. package/lib/init/template/xsuaa/index.js +1 -1
  78. package/lib/mtx/auth_manager.js +7 -5
  79. package/lib/mtx/util/errors.js +4 -4
  80. package/lib/util/fs.js +1 -1
  81. package/lib/util/jsonc.js +1 -1
  82. package/lib/util/jsonc.pegjs +6 -2
  83. package/npm-shrinkwrap.json +78 -149
  84. package/package.json +1 -1
@@ -1,618 +1,653 @@
1
1
  const {
2
- cdsName,
3
- nameFromPath,
4
- pathAndMethod,
5
- serviceName,
2
+ cdsName,
3
+ nameFromPath,
4
+ pathAndMethod,
5
+ serviceName,
6
6
  } = require("./utilities");
7
7
 
8
8
  module.exports = { importOpenAPI };
9
9
 
10
10
  // we are not interested (yet) in HEAD, OPTIONS, TRACE
11
11
  const IS_METHOD = {
12
- delete: true,
13
- get: true,
14
- patch: true,
15
- post: true,
16
- put: true,
12
+ delete: true,
13
+ get: true,
14
+ patch: true,
15
+ post: true,
16
+ put: true,
17
17
  };
18
18
 
19
19
  const STANDARD_HEADERS = [
20
- "accept",
21
- "accept-encoding",
22
- "accept-language",
23
- "authorization",
24
- "content-type",
25
- "if-match",
20
+ "accept",
21
+ "accept-encoding",
22
+ "accept-language",
23
+ "authorization",
24
+ "content-type",
25
+ "if-match",
26
26
  ];
27
27
 
28
28
  function importOpenAPI(input) {
29
- const csn = {
30
- definitions: {},
31
- meta: { creator: "cds-import-openapi" },
32
- };
33
-
34
- const context = {
35
- serviceName: serviceName(input.info?.title || "TODO.service"),
36
- oasVersion: input.openapi || input.swagger,
37
- v3: !!input.openapi,
38
- schemas: input.openapi ? input.components?.schemas : input.definitions,
39
- anonymous: [],
40
- JSON: false,
41
- };
42
-
43
- //TODO: complain if neither swagger nor openapi3, i.e. context.oasVersion is undefined
44
- const service = {
45
- kind: "service",
46
- "@Capabilities.BatchSupported": false,
47
- "@Capabilities.KeyAsSegmentSupported": true,
48
- };
49
- if (input.info?.title) service["@Core.Description"] = input.info.title;
50
- if (input.info?.version) service["@Core.SchemaVersion"] = input.info.version;
51
- if (input.info?.description)
52
- service["@Core.LongDescription"] = input.info.description;
53
- csn.definitions[context.serviceName] = service;
54
-
55
- operations(context, csn, input);
56
-
57
- reuseTypes(context, csn);
58
-
59
- anonymousTypes(context, csn);
60
-
61
- return csn;
29
+ const csn = {
30
+ definitions: {},
31
+ meta: { creator: "cds-import-openapi" },
32
+ };
33
+
34
+ const context = {
35
+ serviceName: serviceName(input.info?.title || "TODO.service"),
36
+ oasVersion: input.openapi || input.swagger,
37
+ v3: !!input.openapi,
38
+ schemas: input.openapi ? input.components?.schemas : input.definitions,
39
+ anonymous: [],
40
+ JSON: false,
41
+ };
42
+
43
+ //TODO: complain if neither swagger nor openapi3, i.e. context.oasVersion is undefined
44
+ const service = {
45
+ kind: "service",
46
+ "@Capabilities.BatchSupported": false,
47
+ "@Capabilities.KeyAsSegmentSupported": true,
48
+ };
49
+ if (input.info?.title) service["@Core.Description"] = input.info.title;
50
+ if (input.info?.version) service["@Core.SchemaVersion"] = input.info.version;
51
+ if (input.info?.description)
52
+ service["@Core.LongDescription"] = input.info.description;
53
+ csn.definitions[context.serviceName] = service;
54
+
55
+ operations(context, csn, input);
56
+
57
+ reuseTypes(context, csn);
58
+
59
+ anonymousTypes(context, csn);
60
+
61
+ return csn;
62
62
  }
63
63
 
64
64
  function reuseTypes(context, csn) {
65
- for (const [name, schema] of Object.entries(context.schemas || {})) {
66
- const typeName = `${context.serviceName}_types.${cdsName(name)}`;
67
- csn.definitions[typeName] = cdsType(context, schema, false, true);
68
- csn.definitions[typeName].kind = "type";
69
- delete csn.definitions[typeName].notNull;
70
- }
65
+ for (const [name, schema] of Object.entries(context.schemas || {})) {
66
+ const typeName = `${context.serviceName}_types.${cdsName(name)}`;
67
+ csn.definitions[typeName] = cdsType(context, schema, false, true);
68
+ csn.definitions[typeName].kind = "type";
69
+ delete csn.definitions[typeName].notNull;
70
+ }
71
71
  }
72
72
 
73
73
  function anonymousTypes(context, csn) {
74
- for (const a of context.anonymous) {
75
- csn.definitions[a.name] = a.type;
76
- csn.definitions[a.name].kind = "type";
77
- }
78
- if (context.JSON) {
79
- csn.definitions["common.JSON"] = { kind: "type", type: "cds.LargeString" };
80
- }
74
+ for (const a of context.anonymous) {
75
+ csn.definitions[a.name] = a.type;
76
+ csn.definitions[a.name].kind = "type";
77
+ }
78
+ if (context.JSON) {
79
+ csn.definitions["common.JSON"] = { kind: "type", type: "cds.LargeString" };
80
+ }
81
81
  }
82
82
 
83
83
  function operations(context, csn, input) {
84
- const reuseParameters = context.v3
85
- ? input.components?.parameters
86
- : input.parameters;
87
- const reuseResponses = context.v3
88
- ? input.components?.responses
89
- : input.responses;
90
- for (const [path, item] of Object.entries(input.paths || {})) {
91
- const globalParameters = item.parameters || [];
92
- for (const [method, operation] of Object.entries(item)) {
93
- if (!IS_METHOD[method]) continue;
94
- const cdsOperation = {
95
- kind: method === "get" ? "function" : "action",
96
- params: {},
97
- };
98
- if (operation.tags) cdsOperation["@Common.Label"] = operation.tags[0];
99
- if (operation.summary)
100
- cdsOperation["@Core.Description"] = operation.summary;
101
- if (operation.description)
102
- cdsOperation["@Core.LongDescription"] = operation.description;
103
- if (cdsOperation.kind === "action" && method !== "post")
104
- cdsOperation["@openapi.method"] = method.toUpperCase();
105
- cdsOperation["@openapi.path"] = path;
106
-
107
- let bodyParam;
108
-
109
- for (let param of globalParameters.concat(operation.parameters || [])) {
110
- if (param.$ref) {
111
- // resolve reuse parameter
112
- const expectedPrefix = context.v3
113
- ? "#/components/parameters/"
114
- : "#/parameters/";
115
- if (!param.$ref.startsWith(expectedPrefix)) {
116
- throw new Error(`TODO: unexpected reference ${param.$ref} in parameter for ${method} of ${path}`);
117
- }
118
- param = reuseParameters[param.$ref.substring(expectedPrefix.length)];
119
- }
120
-
121
- if (param.in === "body") {
122
- bodyParam = param;
123
- continue;
124
- }
125
- if (STANDARD_HEADERS.includes(param.name.toLowerCase())) continue;
126
-
127
- const cdsParam = cdsType(
128
- context,
129
- context.v3 ? param?.schema : param,
130
- false,
131
- false,
132
- true
133
- );
134
- if (param.description) cdsParam["@description"] = param.description;
135
- cdsParam["@openapi.in"] = param.in;
136
-
137
- // only add the annotation "@openapi.explode" for true scenario
138
- if (param.explode || param.style === "form")
139
- cdsParam["@openapi.explode"] = true;
140
- if (!param.explode)
141
- delete cdsParam["@openapi.explode"];
142
-
143
- if ((param.in === "path" && param.style && param.style !== "simple") ||
144
- (param.in === "query" && param.style && param.style !== "form")
145
- )
146
- cdsParam["@openapi.style"] = param.style;
147
- if (param.in === "query" && param.collectionFormat === "ssv")
148
- cdsParam["@openapi.style"] = "spaceDelimited";
149
- if (param.in === "query" && param.collectionFormat === "pipes")
150
- cdsParam["@openapi.style"] = "pipeDelimited";
151
-
152
- if (param.in === "query" && param.allowReserved === true)
153
- cdsParam["@openapi.allowReserved"] = true;
154
-
155
- if (param.required && param.in !== "path")
156
- cdsParam["@openapi.required"] = true;
157
-
158
- if (!param.required) {
159
- delete param.notNull;
160
- if (param.default !== undefined)
161
- cdsParam["default"] = { "val": param.default };
162
- }
163
- const name = cdsName(param.name);
164
- if (name !== param.name) cdsParam["@openapi.name"] = param.name;
165
- cdsOperation.params[name] = cdsParam;
166
- }
167
-
168
- const schema = context.v3
169
- ? requestBodySchema(context, operation.requestBody, input.components)
170
- : v2Schema(context, bodyParam?.schema, operation.consumes);
171
- if (schema) {
172
- cdsOperation.params.body = cdsType(context, schema);
173
- cdsOperation.params.body["@openapi.in"] = "body";
84
+ const reuseParameters = context.v3
85
+ ? input.components?.parameters
86
+ : input.parameters;
87
+ const reuseResponses = context.v3
88
+ ? input.components?.responses
89
+ : input.responses;
90
+ for (const [path, item] of Object.entries(input.paths || {})) {
91
+ const globalParameters = item.parameters || [];
92
+ for (const [method, operation] of Object.entries(item)) {
93
+ if (!IS_METHOD[method]) continue;
94
+ const cdsOperation = {
95
+ kind: method === "get" ? "function" : "action",
96
+ params: {},
97
+ };
98
+ if (operation.tags) cdsOperation["@Common.Label"] = operation.tags[0];
99
+ if (operation.summary)
100
+ cdsOperation["@Core.Description"] = operation.summary;
101
+ if (operation.description)
102
+ cdsOperation["@Core.LongDescription"] = operation.description;
103
+ if (cdsOperation.kind === "action" && method !== "post")
104
+ cdsOperation["@openapi.method"] = method.toUpperCase();
105
+ cdsOperation["@openapi.path"] = path;
106
+
107
+ let bodyParam;
108
+
109
+ for (let param of globalParameters.concat(operation.parameters || [])) {
110
+ if (param.$ref) {
111
+ // resolve reuse parameter
112
+ const expectedPrefix = context.v3
113
+ ? "#/components/parameters/"
114
+ : "#/parameters/";
115
+ if (!param.$ref.startsWith(expectedPrefix)) {
116
+ throw new Error(
117
+ `TODO: unexpected reference ${param.$ref} in parameter for ${method} of ${path}`,
118
+ );
119
+ }
120
+ param = reuseParameters[param.$ref.substring(expectedPrefix.length)];
174
121
  }
175
122
 
176
- if (!operation.responses)
177
- throw new Error(`TODO: no responses for path ${path}, method ${method}`);
123
+ if (param.in === "body") {
124
+ bodyParam = param;
125
+ continue;
126
+ }
127
+ if (STANDARD_HEADERS.includes(param.name.toLowerCase())) continue;
128
+
129
+ const cdsParam = cdsType(
130
+ context,
131
+ context.v3 ? param?.schema : param,
132
+ false,
133
+ false,
134
+ true,
135
+ );
136
+ if (param.description) cdsParam["@description"] = param.description;
137
+ cdsParam["@openapi.in"] = param.in;
138
+
139
+ // only add the annotation "@openapi.explode" for true scenario
140
+ if (param.explode || param.style === "form")
141
+ cdsParam["@openapi.explode"] = true;
142
+ if (!param.explode) delete cdsParam["@openapi.explode"];
178
143
 
179
- const successCode = Object.keys(operation.responses || {}).find((r) =>
180
- r.startsWith("2")
144
+ if (
145
+ (param.in === "path" && param.style && param.style !== "simple") ||
146
+ (param.in === "query" && param.style && param.style !== "form")
147
+ )
148
+ cdsParam["@openapi.style"] = param.style;
149
+ if (param.in === "query" && param.collectionFormat === "ssv")
150
+ cdsParam["@openapi.style"] = "spaceDelimited";
151
+ if (param.in === "query" && param.collectionFormat === "pipes")
152
+ cdsParam["@openapi.style"] = "pipeDelimited";
153
+
154
+ if (param.in === "query" && param.allowReserved === true)
155
+ cdsParam["@openapi.allowReserved"] = true;
156
+
157
+ if (param.required && param.in !== "path")
158
+ cdsParam["@openapi.required"] = true;
159
+
160
+ if (!param.required) {
161
+ delete param.notNull;
162
+ if (param.default !== undefined)
163
+ cdsParam["default"] = { val: param.default };
164
+ }
165
+ const name = cdsName(param.name);
166
+ if (name !== param.name) cdsParam["@openapi.name"] = param.name;
167
+ cdsOperation.params[name] = cdsParam;
168
+ }
169
+
170
+ const schema = context.v3
171
+ ? requestBodySchema(context, operation.requestBody, input.components)
172
+ : v2Schema(context, bodyParam?.schema, operation.consumes);
173
+ if (schema) {
174
+ cdsOperation.params.body = cdsType(context, schema);
175
+ cdsOperation.params.body["@openapi.in"] = "body";
176
+ }
177
+
178
+ if (!operation.responses)
179
+ throw new Error(
180
+ `TODO: no responses for path ${path}, method ${method}`,
181
+ );
182
+
183
+ const successCode = Object.keys(operation.responses || {}).find((r) =>
184
+ r.startsWith("2"),
185
+ );
186
+ if (successCode) {
187
+ let response = operation.responses[successCode];
188
+ if (response?.$ref) {
189
+ // resolve reuse response
190
+ const expectedPrefix = context.v3
191
+ ? "#/components/responses/"
192
+ : "#/responses/";
193
+ if (!response.$ref.startsWith(expectedPrefix))
194
+ throw new Error(
195
+ `TODO: unexpected reference ${response.$ref} in ${successCode} response for method ${method} of path ${path}`,
181
196
  );
182
- if (successCode) {
183
- let response = operation.responses[successCode];
184
- if (response.$ref) {
185
- // resolve reuse response
186
- const expectedPrefix = context.v3
187
- ? "#/components/responses/"
188
- : "#/responses/";
189
- if (!response.$ref.startsWith(expectedPrefix))
190
- throw new Error(`TODO: unexpected reference ${response.$ref} in ${successCode} response for method ${method} of path ${path}`);
191
- else
192
- response =
193
- reuseResponses[response.$ref.substring(expectedPrefix.length)];
194
- }
195
- const responseSchema = context.v3
196
- ? v3Schema(context, response)
197
- : v2Schema(context, response.schema, operation.produces);
198
- if (responseSchema)
199
- cdsOperation.returns = cdsType(context, responseSchema);
200
- else if (method === "get")
201
- cdsOperation.returns = { type: "cds.Boolean" };
202
- } else if (method === "get")
203
- cdsOperation.returns = { type: "cds.Boolean" };
204
-
205
- const operationName = `${context.serviceName}.${nameFromPath(
206
- path,
207
- method
208
- )}`;
209
-
210
- if (csn.definitions[operationName]) {
211
- const existing = pathAndMethod(csn.definitions[operationName]);
212
- throw new Error(`Name collision: same name ${operationName} for method ${method} of path ${path} and method ${existing.method.toLowerCase()} of path ${existing.path}`);
213
- }
214
- csn.definitions[operationName] = cdsOperation;
197
+ else
198
+ response =
199
+ reuseResponses[response.$ref.substring(expectedPrefix.length)];
215
200
  }
201
+ const responseSchema = context.v3
202
+ ? v3Schema(context, response)
203
+ : v2Schema(context, response.schema, operation.produces);
204
+ if (responseSchema)
205
+ cdsOperation.returns = cdsType(context, responseSchema);
206
+ else if (method === "get")
207
+ cdsOperation.returns = { type: "cds.Boolean" };
208
+ } else if (method === "get")
209
+ cdsOperation.returns = { type: "cds.Boolean" };
210
+
211
+ const operationName = `${context.serviceName}.${nameFromPath(
212
+ path,
213
+ method,
214
+ )}`;
215
+
216
+ if (csn.definitions[operationName]) {
217
+ const existing = pathAndMethod(csn.definitions[operationName]);
218
+ throw new Error(
219
+ `Name collision: same name ${operationName} for method ${method} of path ${path} and method ${existing.method.toLowerCase()} of path ${existing.path
220
+ }`,
221
+ );
222
+ }
223
+ csn.definitions[operationName] = cdsOperation;
216
224
  }
225
+ }
217
226
  }
218
227
 
219
228
  function requestBodySchema(context, requestBody, components) {
220
- while (requestBody?.$ref) {
221
- const expectedPrefix = "#/components/requestBodies/";
222
- if (!requestBody.$ref.startsWith(expectedPrefix)) {
223
- requestBody = undefined;
224
- throw new Error(`TODO: unexpected request body reference ${requestBody.$ref}`)
225
- }
226
- else {
227
- requestBody =
228
- components?.requestBodies[
229
- requestBody.$ref.substring(expectedPrefix.length)
230
- ];
231
- }
229
+ while (requestBody?.$ref) {
230
+ const expectedPrefix = "#/components/requestBodies/";
231
+ if (!requestBody.$ref.startsWith(expectedPrefix)) {
232
+ throw new Error(
233
+ `Unexpected request body reference ${requestBody.$ref}`,
234
+ );
235
+ } else {
236
+ requestBody =
237
+ components?.requestBodies[
238
+ requestBody.$ref.substring(expectedPrefix.length)
239
+ ];
232
240
  }
241
+ }
233
242
 
234
- return v3Schema(context, requestBody);
243
+ return v3Schema(context, requestBody);
235
244
  }
236
245
 
237
246
  function v3Schema(context, body) {
238
- if (!body?.content) return undefined;
247
+ if (!body?.content) return undefined;
239
248
 
240
- const contentTypes = Object.keys(body.content);
241
- if (contentTypes.includes("application/json"))
242
- return body.content["application/json"].schema;
249
+ const contentTypes = Object.keys(body.content);
250
+ if (contentTypes.includes("application/json"))
251
+ return body.content["application/json"].schema;
243
252
 
244
- if (contentTypes.length === 0) return undefined;
253
+ if (contentTypes.length === 0) return undefined;
245
254
 
246
- if (contentTypes.length > 1)
247
- context.messages.push({
248
- message: `Multiple requestBody content-types not including application/json`,
249
- input: contentTypes,
250
- });
255
+ // if (contentTypes.length > 1)
256
+ // context.messages.push({
257
+ // message: `Multiple requestBody content-types not including application/json`,
258
+ // input: contentTypes,
259
+ // });
251
260
 
252
- const contentType = contentTypes[0];
261
+ const contentType = contentTypes[0];
253
262
 
254
- return {
255
- $contentType: contentType,
256
- ...body.content[contentType]?.schema,
257
- };
263
+ return {
264
+ $contentType: contentType,
265
+ ...body.content[contentType]?.schema,
266
+ };
258
267
  }
259
268
 
260
269
  function v2Schema(context, schema, contentTypes) {
261
- if (
262
- !schema ||
263
- !contentTypes ||
264
- contentTypes.includes("application/json") ||
265
- contentTypes.includes("application/json;charset=utf-8") ||
266
- contentTypes.includes("application/json;charset=UTF-8")
267
- )
268
- return schema;
269
-
270
- // take the first content type if there are multiple
271
- const contentType = contentTypes[0];
272
-
273
- return {
274
- $contentType: contentType,
275
- ...schema,
276
- };
270
+ if (
271
+ !schema ||
272
+ !contentTypes ||
273
+ contentTypes.includes("application/json") ||
274
+ contentTypes.includes("application/json;charset=utf-8") ||
275
+ contentTypes.includes("application/json;charset=UTF-8")
276
+ )
277
+ return schema;
278
+
279
+ // take the first content type if there are multiple
280
+ const contentType = contentTypes[0];
281
+
282
+ return {
283
+ $contentType: contentType,
284
+ ...schema,
285
+ };
277
286
  }
278
287
 
279
288
  function cdsType(
280
- context,
281
- schema,
282
- arrayItem = false,
283
- namedType = false,
284
- forParameter = false
289
+ context,
290
+ schema,
291
+ arrayItem = false,
292
+ namedType = false,
293
+ forParameter = false,
285
294
  ) {
286
- let type = {};
287
- let hasIncludes = false;
288
-
289
- if (!arrayItem) if (schema.title) type["@title"] = schema.title;
290
- if (schema.description) type["@description"] = schema.description;
291
- if (schema.$contentType && schema.$contentType !== '*/*')
292
- type['@openapi.contentType'] = schema.$contentType;
293
-
294
-
295
- if (schema.$ref) {
296
- const refType = referencedType(context, schema.$ref);
297
- let nRefType = refType;
298
- if (refType.schema?.$ref)
299
- nRefType = indirectlyReferencedType(context, refType.schema);
300
- if (namedType && normalizeSchemaType(nRefType.schema) === "object") {
301
- type.kind = "type";
302
- type.includes = [refType.name];
303
- type.elements = {};
295
+ let type = {};
296
+ let hasIncludes = false;
297
+
298
+ if (!arrayItem) if (schema.title) type["@title"] = schema.title;
299
+ if (schema.description) type["@description"] = schema.description;
300
+ if (schema.$contentType && schema.$contentType !== "*/*")
301
+ type["@openapi.contentType"] = schema.$contentType;
302
+
303
+ if (schema.$ref) {
304
+ const refType = referencedType(context, schema.$ref);
305
+ let nRefType = refType;
306
+ if (refType.schema?.$ref)
307
+ nRefType = indirectlyReferencedType(context, refType.schema);
308
+ if (namedType && normalizeSchemaType(nRefType.schema) === "object") {
309
+ type.kind = "type";
310
+ type.includes = [refType.name];
311
+ type.elements = {};
312
+ } else {
313
+ type.type = refType.name;
314
+ if (schema.maxLength) type.length = schema.maxLength;
315
+ }
316
+ return type;
317
+ }
318
+
319
+ let schemaType = normalizeSchemaType(schema);
320
+
321
+ switch (schemaType) {
322
+ case "array":
323
+ //TODO: complain if "xml"
324
+ if (schema.items) {
325
+ const itemsType = cdsType(
326
+ context,
327
+ schema.items,
328
+ true,
329
+ false,
330
+ forParameter,
331
+ );
332
+ const annotations = Object.keys(itemsType).filter((k) =>
333
+ k.startsWith("@"),
334
+ );
335
+ // make anonymous type for item if
336
+ // - item has annotation
337
+ // - item has default
338
+ // - item is itself an array
339
+ if (annotations.length > 0 || itemsType.default || itemsType.items) {
340
+ type.items = anonymousType(context, itemsType);
304
341
  } else {
305
- type.type = refType.name;
306
- if (schema.maxLength) type.length = schema.maxLength;
342
+ type.items = itemsType;
307
343
  }
308
- return type;
309
- }
310
-
311
- let schemaType = normalizeSchemaType(schema);
312
-
313
- switch (schemaType) {
314
- case "array":
315
- //TODO: complain if "xml"
316
- if (schema.items) {
317
- const itemsType = cdsType(
318
- context,
319
- schema.items,
320
- true,
321
- false,
322
- forParameter
323
- );
324
- const annotations = Object.keys(itemsType).filter((k) =>
325
- k.startsWith("@")
326
- );
327
- // make anonymous type for item if item has annotations
328
- if (annotations.length > 0) {
329
- type.items = anonymousType(context, itemsType);
330
- } else {
331
- type.items = itemsType;
332
- }
333
- } else {
334
- type = someJSON(context, schema, arrayItem, type);
335
- }
336
- break;
337
-
338
- case "boolean":
339
- type.type = "cds.Boolean";
340
- addDefault(type, schema, forParameter);
341
- break;
342
-
343
- case "file":
344
- type.type = "cds.String";
345
- break;
346
-
347
- case "integer":
348
- type.type = "cds.Integer";
349
- if (schema.format === "int64") type.type = "cds.Integer64";
350
- addDefault(type, schema, forParameter);
351
- addPrimitiveExample(type, schema);
352
- break;
353
-
354
- case "number":
355
- type.type = "cds.Decimal";
356
- if (schema.format === "double" || schema.format === "float")
357
- type.type = "cds.Double";
358
- addDefault(type, schema, forParameter);
359
- addPrimitiveExample(type, schema);
360
- break;
361
-
362
- case "object":
363
- type.elements = {};
364
- //TODO: discriminator(context, type, schema);
365
- if (schema.allOf || schema.anyOf || schema.oneOf) {
366
- const xOf = (schema.allOf || []).concat(
367
- schema.anyOf || [],
368
- schema.oneOf || []
369
- );
370
- for (const subSchema of xOf) {
371
- if (subSchema.$ref) {
372
- hasIncludes = true;
373
- const refType = referencedType(context, subSchema.$ref);
374
- if (!type.includes) type.includes = [];
375
- type.includes.push(refType.name);
376
- } else if (subSchema.type === "object" || subSchema.properties) {
377
- //TODO: what if subSchema has allOf/...? Better recurse here and "merge" the types?
378
- structElements(context, type, subSchema);
379
- } else {
380
- // should not get here
381
- throw new Error(`Error: object with non-object sub-schema`);
382
- }
383
- }
384
- }
385
-
386
- structElements(context, type, schema);
387
-
388
- if (!namedType && hasIncludes) {
389
- type = anonymousType(context, type);
390
- }
391
-
392
- break;
393
-
394
- case "string":
395
- switch (schema.format) {
396
- case "binary":
397
- type.type = "cds.LargeBinary";
398
- if (schema.maxLength) type.length = schema.maxLength;
399
- break;
400
- case "date":
401
- type.type = "cds.Date";
402
- break;
403
- case "date-time":
404
- type.type = "cds.Timestamp";
405
- break;
406
- case "time":
407
- type.type = "cds.Time";
408
- break;
409
- case "uuid":
410
- type.type = "cds.UUID";
411
- break;
412
- default:
413
- type.type = "cds.String";
414
- if (schema.maxLength) type.length = schema.maxLength;
415
- }
416
- if (Array.isArray(schema.enum)) {
417
- type["@assert.range"] = true;
418
- type.enum = Object.fromEntries(
419
- schema.enum.map((val) => {
420
- const key = cdsName(val);
421
- return key === val ? [val, {}] : [key, { val }];
422
- })
423
- );
424
- }
425
- addDefault(type, schema, forParameter);
426
- addPrimitiveExample(type, schema);
427
- break;
428
-
429
- case undefined:
430
- schemaType = bestMatchingType(context, schema);
431
- if (
432
- (!namedType || schemaType !== "object") && schema.allOf && schema.allOf.length === 1 &&
433
- Object.keys(schema).filter(
434
- (k) => !["description", "nullable"].includes(k) && !k.startsWith("x-")
435
- ).length === 1) {
436
- const normalizedSchema = { ...schema.allOf[0] };
437
- if (schema.description) normalizedSchema.description = schema.description;
438
- type = cdsType(context, normalizedSchema, arrayItem, namedType, forParameter);
439
- break;
440
- }
441
- if (schema.allOf && schema.allOf.length === 2 && schema.allOf[0].$ref && !schema.allOf[1].$ref && normalizeSchemaType(schema.allOf[1]) !== "object") {
442
- const normalizedSchema = Object.assign(
443
- { ...schema.allOf[0] },
444
- schema.allOf[1]
445
- );
446
- type = cdsType(
447
- context,
448
- normalizedSchema,
449
- arrayItem,
450
- namedType,
451
- forParameter
452
- );
453
- break;
454
- }
455
-
456
- if (schemaType) {
457
- schema.type = schemaType;
458
- type = cdsType(context, schema, arrayItem, namedType, forParameter);
459
- break;
460
- }
461
-
462
- // last resort
463
- // eslint-disable-next-line no-fallthrough
344
+ } else {
345
+ type = someJSON(context, schema, arrayItem, type);
346
+ }
347
+ break;
348
+
349
+ case "boolean":
350
+ type.type = "cds.Boolean";
351
+ addDefault(type, schema, forParameter);
352
+ break;
353
+
354
+ case "file":
355
+ type.type = "cds.String";
356
+ break;
357
+
358
+ case "integer":
359
+ type.type = "cds.Integer";
360
+ if (schema.format === "int64") type.type = "cds.Integer64";
361
+ addDefault(type, schema, forParameter);
362
+ addPrimitiveExample(type, schema);
363
+ break;
364
+
365
+ case "number":
366
+ type.type = "cds.Decimal";
367
+ if (schema.format === "double" || schema.format === "float")
368
+ type.type = "cds.Double";
369
+ addDefault(type, schema, forParameter);
370
+ addPrimitiveExample(type, schema);
371
+ break;
372
+
373
+ case "object":
374
+ type.elements = {};
375
+ //TODO: discriminator(context, type, schema);
376
+
377
+ if (schema.anyOf) {
378
+ type["@openapi.anyOf"] = JSON.stringify(schema.anyOf);
379
+ type["@open"] = true;
380
+ }
381
+ if (schema.oneOf) {
382
+ type["@openapi.oneOf"] = JSON.stringify(schema.oneOf);
383
+ type["@open"] = true;
384
+ }
385
+
386
+ for (const subSchema of schema.allOf || []) {
387
+ if (subSchema.$ref) {
388
+ hasIncludes = true;
389
+ const refType = referencedType(context, subSchema.$ref);
390
+ if (!type.includes) type.includes = [];
391
+ type.includes.push(refType.name);
392
+ } else if (subSchema.type === "object" || subSchema.properties) {
393
+ //TODO: what if subSchema has allOf/...? Better recurse here and "merge" the types?
394
+ structElements(context, type, subSchema);
395
+ } else {
396
+ // should not get here
397
+ throw new Error(`Error: object with non-object sub-schema`);
398
+ }
399
+ }
400
+
401
+ structElements(context, type, schema);
402
+
403
+ if (!namedType && hasIncludes) {
404
+ type = anonymousType(context, type);
405
+ }
406
+
407
+ break;
408
+
409
+ case "string":
410
+ switch (schema.format) {
411
+ case "binary":
412
+ type.type = "cds.LargeBinary";
413
+ break;
414
+ case "date":
415
+ type.type = "cds.Date";
416
+ break;
417
+ case "date-time":
418
+ type.type = "cds.Timestamp";
419
+ break;
420
+ case "time":
421
+ type.type = "cds.Time";
422
+ break;
423
+ case "uuid":
424
+ type.type = "cds.UUID";
425
+ break;
464
426
  default:
465
- type = someJSON(context, schema, arrayItem, type);
466
- break;
467
- }
468
- return type;
427
+ type.type = "cds.String";
428
+ if (schema.maxLength) type.length = schema.maxLength;
429
+ }
430
+ if (Array.isArray(schema.enum)) {
431
+ type["@assert.range"] = true;
432
+ type.enum = Object.fromEntries(
433
+ schema.enum
434
+ .filter((val) => val !== null)
435
+ .map((val) => {
436
+ const key = cdsName(val);
437
+ return key === val ? [val, {}] : [key, { val }];
438
+ }),
439
+ );
440
+ }
441
+ if (schema.pattern) type["@assert.format"] = schema.pattern;
442
+ addDefault(type, schema, forParameter);
443
+ addPrimitiveExample(type, schema);
444
+ break;
445
+
446
+ case undefined:
447
+ schemaType = bestMatchingType(context, schema);
448
+ if (
449
+ (!namedType || schemaType !== "object") &&
450
+ schema.allOf &&
451
+ schema.allOf.length === 1 &&
452
+ Object.keys(schema).filter(
453
+ (k) =>
454
+ !["description", "nullable"].includes(k) && !k.startsWith("x-"),
455
+ ).length === 1
456
+ ) {
457
+ const normalizedSchema = { ...schema.allOf[0] };
458
+ if (schema.description)
459
+ normalizedSchema.description = schema.description;
460
+ type = cdsType(
461
+ context,
462
+ normalizedSchema,
463
+ arrayItem,
464
+ namedType,
465
+ forParameter,
466
+ );
467
+ break;
468
+ }
469
+ if (
470
+ schema.allOf &&
471
+ schema.allOf.length === 2 &&
472
+ schema.allOf[0].$ref &&
473
+ !schema.allOf[1].$ref &&
474
+ normalizeSchemaType(schema.allOf[1]) !== "object"
475
+ ) {
476
+ const normalizedSchema = Object.assign(
477
+ { ...schema.allOf[0] },
478
+ schema.allOf[1],
479
+ );
480
+ type = cdsType(
481
+ context,
482
+ normalizedSchema,
483
+ arrayItem,
484
+ namedType,
485
+ forParameter,
486
+ );
487
+ break;
488
+ }
489
+
490
+ if (schemaType) {
491
+ schema.type = schemaType;
492
+ type = cdsType(context, schema, arrayItem, namedType, forParameter);
493
+ break;
494
+ }
495
+
496
+ // last resort
497
+ // eslint-disable-next-line no-fallthrough
498
+ default:
499
+ type = someJSON(context, schema, arrayItem, type);
500
+ break;
501
+ }
502
+ return type;
469
503
  }
470
504
 
471
505
  function someJSON(context, schema, arrayItem, type) {
472
- context.JSON = true;
473
- const jsonSchema = minimalSchema(schema);
474
- if (arrayItem && jsonSchema !== "{}") {
475
- type = anonymousType(context, {
476
- type: "common.JSON",
477
- "@openapi.schema": jsonSchema,
478
- });
479
- }
480
- else {
481
- type.type = "common.JSON";
482
- if (jsonSchema !== "{}") type["@openapi.schema"] = jsonSchema;
483
- }
484
- return type;
506
+ context.JSON = true;
507
+ const jsonSchema = minimalSchema(schema);
508
+ if (arrayItem && jsonSchema !== "{}") {
509
+ type = anonymousType(context, {
510
+ type: "common.JSON",
511
+ "@openapi.schema": jsonSchema,
512
+ });
513
+ } else {
514
+ type.type = "common.JSON";
515
+ if (jsonSchema !== "{}") type["@openapi.schema"] = jsonSchema;
516
+ }
517
+ return type;
485
518
  }
486
519
 
487
520
  function addDefault(type, schema, forParameter) {
488
- if (schema.default !== undefined) type.default = { val: schema.default };
489
- if (forParameter) return;
521
+ if (schema.default !== undefined) type.default = { val: schema.default };
522
+ if (forParameter) return;
490
523
  }
491
524
 
492
525
  function addPrimitiveExample(type, schema) {
493
- const example = schema.examples?.[0] || schema.example;
494
- if (!example || example.$ref) return;
495
- type["@Core.Example.$Type"] = "Core.PrimitiveExampleValue";
496
- type["@Core.Example.Value"] = example;
526
+ const example = schema.examples?.[0] || schema.example;
527
+ if (!example || example.$ref) return;
528
+ type["@Core.Example.$Type"] = "Core.PrimitiveExampleValue";
529
+ type["@Core.Example.Value"] = example;
497
530
  }
498
531
 
499
532
  function minimalSchema(schema) {
500
- const s = { ...schema };
501
- delete s.title;
502
- delete s.description;
503
- delete s.$contentType;
504
- return JSON.stringify(s);
533
+ const s = { ...schema };
534
+ delete s.title;
535
+ delete s.description;
536
+ delete s.$contentType;
537
+ return JSON.stringify(s);
505
538
  }
506
539
 
507
540
  function anonymousType(context, type) {
508
- //TODO: construct newName from path leading here instead of using a counter
509
- // pro: makes imported models more stable
510
- // con: may cause name clashes
511
- const newName = `${context.serviceName}.anonymous.type${context.anonymous.length}`;
512
- context.anonymous.push({ name: newName, type: type });
513
- return { type: newName };
541
+ //TODO: construct newName from path leading here instead of using a counter
542
+ // pro: makes imported models more stable
543
+ // con: may cause name clashes
544
+ const newName = `${context.serviceName}.anonymous.type${context.anonymous.length}`;
545
+ context.anonymous.push({ name: newName, type: type });
546
+ return { type: newName };
514
547
  }
515
548
 
516
549
  function bestMatchingType(context, schema) {
517
- if (schema.type) return schema.type;
518
- let type;
519
- if (schema.allOf || schema.anyOf || schema.oneOf) {
520
- const xOf = (schema.allOf || []).concat(
521
- schema.anyOf || [],
522
- schema.oneOf || []
523
- );
524
- for (const subSchema of xOf) {
525
- // determine overall type of this construct
526
- if (subSchema.$ref) {
527
- const refType = indirectlyReferencedType(context, subSchema);
528
- if (!refType.schema) return undefined;
529
-
530
- if (refType.schema.allOf || refType.schema.anyOf || refType.schema.oneOf) {
531
- const subType = bestMatchingType(context, refType.schema);
532
- type = betterType(type, { type: subType });
533
- }
534
-
535
- else type = betterType(type, refType.schema);
536
- }
537
- else {
538
- //TODO: nested xOf - not yet encountered
539
- type = betterType(type, subSchema);
540
- }
541
- }
550
+ if (schema.type) return schema.type;
551
+ let type;
552
+ if (schema.allOf || schema.anyOf || schema.oneOf) {
553
+ const xOf = (schema.allOf || []).concat(
554
+ schema.anyOf || [],
555
+ schema.oneOf || [],
556
+ );
557
+ for (const subSchema of xOf) {
558
+ // determine overall type of this construct
559
+ if (subSchema.$ref) {
560
+ const refType = indirectlyReferencedType(context, subSchema);
561
+ if (!refType.schema) return undefined;
562
+
563
+ if (
564
+ refType.schema.allOf ||
565
+ refType.schema.anyOf ||
566
+ refType.schema.oneOf
567
+ ) {
568
+ const subType = bestMatchingType(context, refType.schema);
569
+ type = betterType(type, { type: subType });
570
+ } else type = betterType(type, refType.schema);
571
+ } else {
572
+ //TODO: nested xOf - not yet encountered
573
+ type = betterType(type, subSchema);
574
+ }
542
575
  }
543
- return type;
576
+ }
577
+ return type;
544
578
  }
545
579
 
546
580
  function indirectlyReferencedType(context, subSchema) {
547
- let refType = { schema: subSchema };
548
- let limit = 10;
549
- while (refType.schema?.$ref && limit-- > 0)
550
- refType = referencedType(context, refType.schema.$ref);
551
- return refType;
581
+ let refType = { schema: subSchema };
582
+ let limit = 10;
583
+ while (refType.schema?.$ref && limit-- > 0)
584
+ refType = referencedType(context, refType.schema.$ref);
585
+ return refType;
552
586
  }
553
587
 
554
588
  function referencedType(context, ref) {
555
- const expectedPrefix = context.v3
556
- ? "#/components/schemas/"
557
- : "#/definitions/";
558
- if (!ref.startsWith(expectedPrefix)) {
559
- throw new Error(`TODO: unexpected reference ${ref} in schema.`);
560
- }
561
- const schemaName = ref.substring(expectedPrefix.length);
562
- const schema = context.schemas[schemaName];
563
- return {
564
- name: `${context.serviceName}_types.${cdsName(schemaName)}`,
565
- schema,
566
- };
589
+ const expectedPrefix = context.v3
590
+ ? "#/components/schemas/"
591
+ : "#/definitions/";
592
+ if (!ref.startsWith(expectedPrefix)) {
593
+ throw new Error(`TODO: unexpected reference ${ref} in schema.`);
594
+ }
595
+ const schemaName = decodeURIComponent(ref.substring(expectedPrefix.length));
596
+ const schema = context.schemas[schemaName];
597
+
598
+ return {
599
+ name: `${context.serviceName}_types.${cdsName(schemaName)}`,
600
+ schema,
601
+ };
567
602
  }
568
603
 
569
604
  function normalizeSchemaType(schema) {
570
- if (!schema) return undefined;
571
- if (!schema.type && schema.items) return "array";
572
- if (!schema.type && schema.maxLength) return "string";
573
- if (!schema.type && schema.pattern) return "string";
574
-
575
- if (!schema.type && schema.enum?.every((v) => typeof v === "string"))
576
- return "string";
577
-
605
+ if (!schema) return undefined;
606
+ if (!schema.type && schema.items) return "array";
607
+ if (!schema.type && schema.maxLength) return "string";
608
+ if (!schema.type && schema.pattern) return "string";
609
+
610
+ if (!schema.type && schema.enum?.every((v) => typeof v === "string"))
611
+ return "string";
612
+
613
+ if (
614
+ !schema.type &&
615
+ (schema.properties ||
616
+ schema.patternProperties ||
617
+ schema.additionalProperties ||
618
+ schema.discriminator)
619
+ )
620
+ return "object";
621
+
622
+ let schemaType = schema.type;
623
+ if (Array.isArray(schemaType)) {
624
+ schemaType = schemaType.sort();
625
+ if (schemaType.includes("null")) {
626
+ const index = schemaType.indexOf("null");
627
+ schemaType.splice(index, 1);
628
+ }
629
+ if (schemaType.length === 1) schemaType = schemaType[0];
578
630
  if (
579
- !schema.type &&
580
- (schema.properties ||
581
- schema.patternProperties ||
582
- schema.additionalProperties ||
583
- schema.discriminator)
631
+ schemaType.length === 2 &&
632
+ schemaType[0] === "integer" &&
633
+ schemaType[1] === "number"
584
634
  )
585
- return "object";
586
-
587
- let schemaType = schema.type;
588
- if (Array.isArray(schemaType)) {
589
- schemaType = schemaType.sort();
590
- if (schemaType.includes("null")) {
591
- const index = schemaType.indexOf("null");
592
- schemaType.splice(index, 1);
593
- }
594
- if (schemaType.length === 1) schemaType = schemaType[0];
595
- if (
596
- schemaType.length === 2 &&
597
- schemaType[0] === "integer" &&
598
- schemaType[1] === "number"
599
- )
600
- return "number";
601
- }
602
- return schemaType;
635
+ return "number";
636
+ }
637
+ return schemaType;
603
638
  }
604
639
 
605
640
  //TODO: better name :-)
606
641
  function betterType(currentType, schema) {
607
- const schemaType = normalizeSchemaType(schema);
608
- if (currentType === undefined) return schemaType;
609
- if (currentType === schemaType) return currentType;
610
- if (currentType === "string" && ["integer", "number"].includes(schemaType))
611
- return "string";
612
- if (["integer", "number"].includes(currentType) && schemaType === "string")
613
- return "string";
614
- //TODO: more cases here?
615
- return null;
642
+ const schemaType = normalizeSchemaType(schema);
643
+ if (currentType === undefined) return schemaType;
644
+ if (currentType === schemaType) return currentType;
645
+ if (currentType === "string" && ["integer", "number"].includes(schemaType))
646
+ return "string";
647
+ if (["integer", "number"].includes(currentType) && schemaType === "string")
648
+ return "string";
649
+ //TODO: more cases here?
650
+ return null;
616
651
  }
617
652
 
618
653
  // function discriminator(context, type, schema) {
@@ -633,11 +668,61 @@ function betterType(currentType, schema) {
633
668
  // }
634
669
 
635
670
  function structElements(context, type, schema) {
636
- //TODO: interpret "required"
637
- for (const [prop, propSchema] of Object.entries(schema.properties || {})) {
638
- const name = cdsName(prop);
639
- //TODO: check if property "name" already exists and has identical cdsType, warn otherwise
640
- type.elements[name] = cdsType(context, propSchema);
641
- if (name !== prop) type.elements[name]["@openapi.name"] = prop;
671
+ checkCircularReference(schema);
672
+ const schemaName = Object.keys(context.schemas || {}).find(
673
+ (key) => context.schemas[key] === schema,
674
+ );
675
+ const refPath = `#/components/schemas/${schemaName}`;
676
+ //TODO: interpret "required"
677
+ for (const [prop, propSchema] of Object.entries(schema.properties || {})) {
678
+ const name = cdsName(prop);
679
+ //to detect recursive data types
680
+ if (propSchema?.$ref === refPath || propSchema?.items?.$ref === refPath) {
681
+ console.warn(
682
+ `Recursive data type detected: ${schemaName} for the property ${name}`,
683
+ );
684
+ }
685
+ //TODO: check if property "name" already exists and has identical cdsType, warn otherwise
686
+ type.elements[name] = cdsType(context, propSchema);
687
+ if (name !== prop) type.elements[name]["@openapi.name"] = prop;
688
+ }
689
+ }
690
+
691
+ // function to check circular references and throw error if found
692
+ function checkCircularReference(schema, visited = new Set()) {
693
+ if (schema.$ref) {
694
+ const ref = schema.$ref;
695
+ if (visited.has(ref)) {
696
+ console.warn(`Circular reference detected: ${ref}`);
642
697
  }
643
- }
698
+ visited.add(ref);
699
+ }
700
+
701
+ if (schema.allOf) {
702
+ schema.allOf.forEach((subSchema) =>
703
+ checkCircularReference(subSchema, visited),
704
+ );
705
+ }
706
+
707
+ if (schema.anyOf) {
708
+ schema.anyOf.forEach((subSchema) =>
709
+ checkCircularReference(subSchema, visited),
710
+ );
711
+ }
712
+
713
+ if (schema.oneOf) {
714
+ schema.oneOf.forEach((subSchema) =>
715
+ checkCircularReference(subSchema, visited),
716
+ );
717
+ }
718
+
719
+ if (schema.properties) {
720
+ Object.values(schema.properties).forEach((subSchema) =>
721
+ checkCircularReference(subSchema, visited),
722
+ );
723
+ }
724
+
725
+ if (schema.items) {
726
+ checkCircularReference(schema.items, visited);
727
+ }
728
+ }