@sap/cds-dk 6.7.1 → 6.8.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.
- package/CHANGELOG.md +31 -0
- package/bin/deploy/index.js +1 -1
- package/bin/import.js +5 -3
- package/bin/login.js +29 -9
- package/bin/migrate.js +6 -2
- package/bin/watch.js +16 -0
- package/lib/bind/bindingUtil.js +2 -2
- package/lib/bind/userCdsrcJsonStorageProvider.js +1 -1
- package/lib/client/auth_manager.js +3 -1
- package/lib/client/helper/errors.js +6 -1
- package/lib/client/params.js +22 -20
- package/lib/client/settings_manager.js +17 -3
- package/lib/compile/openapi/csdl2openapi.js +4 -1
- package/lib/import/asyncapi/importAsyncAPI.js +32 -0
- package/lib/import/asyncapi/index.js +15 -0
- package/lib/import/asyncapi/parserContext.js +351 -0
- package/lib/import/asyncapi/utils.js +18 -0
- package/lib/import/common.js +21 -67
- package/lib/import/importapi.js +23 -2
- package/lib/import/message.js +11 -1
- package/lib/import/odata/index.js +14 -11
- package/lib/import/odata/metadataconverter/MetadataConverter.js +35 -35
- package/lib/import/odata/util/MultiMap.js +5 -5
- package/lib/import/odata/v2parser.js +1 -0
- package/lib/import/odata/v4parser.js +1281 -1139
- package/lib/init/constants.js +10 -3
- package/lib/init/template/_merging/registry-mta.js +33 -36
- package/lib/init/template/approuter/files/mtxs-configmap-java.yaml +20 -0
- package/lib/init/template/approuter/files/mtxs-configmap-nodejs.yaml +12 -0
- package/lib/init/template/approuter/index.js +24 -33
- package/lib/init/template/auditlog/files/package.json.hbs +27 -0
- package/lib/init/template/auditlog/index.js +12 -6
- package/lib/init/template/cf-manifest/index.js +1 -1
- package/lib/init/template/connectivity/index.js +1 -1
- package/lib/init/template/destinations/index.js +4 -2
- package/lib/init/template/enterprise-messaging/files/package.json.hbs +19 -0
- package/lib/init/template/enterprise-messaging/index.js +15 -13
- package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +31 -0
- package/lib/init/template/enterprise-messaging-shared/files/mta.yaml.hbs +15 -0
- package/lib/init/template/enterprise-messaging-shared/files/package.json.hbs +22 -0
- package/lib/init/template/enterprise-messaging-shared/files/values.yaml +7 -0
- package/lib/init/template/enterprise-messaging-shared/index.js +51 -0
- package/lib/init/template/extensibility/files/package.json.hbs +20 -0
- package/lib/init/template/extensibility/index.js +4 -16
- package/lib/init/template/file-based-messaging/files/package.json.hbs +19 -0
- package/lib/init/template/file-based-messaging/index.js +25 -0
- package/lib/init/template/hana/files/package.json.hbs +33 -0
- package/lib/init/template/hana/index.js +38 -65
- package/lib/init/template/helm/chart/templates/_deployment_helpers.tpl +0 -12
- package/lib/init/template/helm/chart/templates/_helpers.tpl +1 -121
- package/lib/init/template/helm/index.js +1 -1
- package/lib/init/template/html5-repo/index.js +1 -1
- package/lib/init/template/java/index.js +2 -2
- package/lib/init/template/kibana-logging/files/package.json.hbs +17 -0
- package/lib/init/template/kibana-logging/index.js +6 -19
- package/lib/init/template/lint/index.js +1 -1
- package/lib/init/template/mta/index.js +2 -2
- package/lib/init/template/multitenancy/files/mtxs-configmap-java.yaml +3 -5
- package/lib/init/template/multitenancy/files/mtxs-configmap-nodejs.yaml +3 -10
- package/lib/init/template/multitenancy/files/package.json.hbs +5 -4
- package/lib/init/template/multitenancy/index.js +22 -19
- package/lib/init/template/nodejs/files/.vscode/launch.json +11 -3
- package/lib/init/template/nodejs/index.js +1 -1
- package/lib/init/template/pipeline/index.js +1 -1
- package/lib/init/template/redis-messaging/files/mta.yaml.hbs +14 -0
- package/lib/init/template/redis-messaging/files/package.json.hbs +22 -0
- package/lib/init/template/redis-messaging/index.js +50 -0
- package/lib/init/template/samples/index.js +3 -3
- package/lib/init/template/sqlite/files/package.json.hbs +12 -4
- package/lib/init/template/sqlite/index.js +5 -1
- package/lib/init/template/toggles/files/package.json.hbs +20 -0
- package/lib/init/template/toggles/index.js +3 -9
- package/lib/init/template/xsuaa/files/package.json.hbs +28 -0
- package/lib/init/template/xsuaa/index.js +8 -23
- package/lib/init/util/{md5Tracking.js → md5.js} +1 -1
- package/lib/init/util/merge.js +4 -9
- package/lib/init/util/{mvnArchetypeUtil.js → mvn.js} +3 -0
- package/lib/init/util/projectReader.js +25 -107
- package/lib/init/util/xml.js +56 -0
- package/lib/lint/checks.js +1 -1
- package/lib/lint/io.js +1 -1
- package/lib/{init/util/commandUtil.js → util/command.js} +4 -2
- package/lib/{init/util/fsUtil.js → util/fs.js} +2 -2
- package/npm-shrinkwrap.json +335 -2212
- package/package.json +5 -4
- package/lib/init/template/approuter/files/cds.cdsrc.json +0 -7
- package/lib/init/template/approuter/files/cds.cdsrc.json.hbs +0 -9
- package/lib/init/template/approuter/files/cds.package.json +0 -9
- package/lib/init/template/approuter/files/cds.package.json.hbs +0 -11
- package/lib/init/template/auditlog/files/dependencies.package.json +0 -5
- package/lib/init/template/enterprise-messaging/files/cds.package.json +0 -9
- package/lib/init/template/enterprise-messaging/files/cds.profile.package.json.hbs +0 -11
- package/lib/init/template/enterprise-messaging/files/cdsrc.json +0 -7
- package/lib/init/template/enterprise-messaging/files/cdsrc.json.hbs +0 -9
- package/lib/init/template/extensibility/files/cds.package.json +0 -7
- package/lib/init/template/extensibility/files/cds.package.json.hbs +0 -9
- package/lib/init/template/extensibility/files/cdsrc.json +0 -5
- package/lib/init/template/extensibility/files/cdsrc.json.hbs +0 -7
- package/lib/init/template/hana/files/java/cds.json.hbs +0 -7
- package/lib/init/template/hana/files/java/cds.profile.json.hbs +0 -12
- package/lib/init/template/hana/files/java/cds.shortcut.json.hbs +0 -5
- package/lib/init/template/hana/files/java/cds.shortcut.profile.json.hbs +0 -8
- package/lib/init/template/hana/files/nodejs/cds.json.hbs +0 -9
- package/lib/init/template/hana/files/nodejs/cds.profile.json.hbs +0 -14
- package/lib/init/template/hana/files/nodejs/cds.shortcut.json.hbs +0 -7
- package/lib/init/template/hana/files/nodejs/cds.shortcut.profile.json.hbs +0 -10
- package/lib/init/template/hana/files/nodejs/dependencies.package.json +0 -5
- package/lib/init/template/helm/chart/templates/_sapcp_helpers.tpl +0 -103
- package/lib/init/template/kibana-logging/files/cds.profile.package.json.hbs +0 -9
- package/lib/init/template/toggles/files/cds.package.json +0 -7
- package/lib/init/template/toggles/files/cds.package.json.hbs +0 -9
- package/lib/init/template/toggles/files/cdsrc.json +0 -5
- package/lib/init/template/toggles/files/cdsrc.json.hbs +0 -7
- package/lib/init/template/xsuaa/files/cds.cdsrc.json +0 -7
- package/lib/init/template/xsuaa/files/cds.cdsrc.json.hbs +0 -9
- package/lib/init/template/xsuaa/files/cds.package.json +0 -9
- package/lib/init/template/xsuaa/files/cds.package.json.hbs +0 -11
- package/lib/init/template/xsuaa/files/dependencies.package.json +0 -6
- package/lib/init/util/versionCompare.js +0 -73
- /package/lib/init/template/hana/files/{java/db → db}/package.json +0 -0
- /package/lib/init/template/hana/files/{common/src → src}/.hdiconfig +0 -0
- /package/lib/init/template/hana/files/{common/undeploy.json → undeploy.json} +0 -0
- /package/lib/init/util/{templateUtil.js → template.js} +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const { typeRegex } = require('./utils');
|
|
2
|
+
const messages = require("../message").getMessages();
|
|
3
|
+
|
|
4
|
+
module.exports = class ParserContext {
|
|
5
|
+
/**
|
|
6
|
+
* @constructor
|
|
7
|
+
*/
|
|
8
|
+
constructor(content) {
|
|
9
|
+
this.services = this.getServicesFromContent(content);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns services from the asyncapi json object along with the events and annotations.
|
|
14
|
+
* @returns an object with all the services.
|
|
15
|
+
*/
|
|
16
|
+
getServicesFromContent(content) {
|
|
17
|
+
const services = {};
|
|
18
|
+
const serviceAnnotations = this.getServiceAnnotations(content);
|
|
19
|
+
|
|
20
|
+
Object.entries(content.components.messages).forEach(([key, value]) => {
|
|
21
|
+
// uses regex to break event name in different components
|
|
22
|
+
const regexGroups = typeRegex.exec(key);
|
|
23
|
+
if (regexGroups == null) {
|
|
24
|
+
throw new Error(messages.INVALID_MESSAGE_NAME);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const service = regexGroups[1] + "." + regexGroups[2];
|
|
28
|
+
const event = regexGroups[0];
|
|
29
|
+
|
|
30
|
+
// add service to services entry if not present
|
|
31
|
+
if (!(service in services)) {
|
|
32
|
+
services[service] = { 'kind': 'service', ...serviceAnnotations, 'events': {}, 'types': {} };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// add event entry to service
|
|
36
|
+
const eventAnnotations = this.getEventAnnotations(value);
|
|
37
|
+
|
|
38
|
+
let properties = {};
|
|
39
|
+
let types = {};
|
|
40
|
+
|
|
41
|
+
if (value.payload) {
|
|
42
|
+
// gets the schema being referred to in the event
|
|
43
|
+
const eventSchema = this.resolveRef(content, value.payload);
|
|
44
|
+
[properties, types] = this.getPropertiesAndTypes(event, eventSchema);
|
|
45
|
+
}
|
|
46
|
+
services[service].types = types;
|
|
47
|
+
services[service].events[event] = { 'kind': 'event', ...eventAnnotations, elements: properties };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return services;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns annotations of a particular service.
|
|
55
|
+
* @returns an object with all the annotations as key value pairs.
|
|
56
|
+
*/
|
|
57
|
+
getServiceAnnotations(content) {
|
|
58
|
+
const serviceAnnotationsMapping = {
|
|
59
|
+
'x-sap-stateInfo': 'StateInfo', //expected: object
|
|
60
|
+
'x-sap-shortText': 'ShortText' //expected: string
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const infoAnnotationsMapping = {
|
|
64
|
+
'title': 'Title', //expected: string
|
|
65
|
+
'version': 'SchemaVersion', //expected: string
|
|
66
|
+
'description': 'Description' //expected: string
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const annotations = this.getAnnotations(content, serviceAnnotationsMapping);
|
|
70
|
+
|
|
71
|
+
if (content.info) {
|
|
72
|
+
for (const [key, value] of Object.entries(infoAnnotationsMapping)) {
|
|
73
|
+
if (content.info[key] && !(content.info[key] instanceof Object)) {
|
|
74
|
+
// info values will always be in string format
|
|
75
|
+
annotations['@AsyncAPI.' + value] = content.info[key];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return annotations;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns annotations of a particular event. Here the parameter content is event content.
|
|
85
|
+
* @returns an object with all the annotations as key value pairs.
|
|
86
|
+
*/
|
|
87
|
+
getEventAnnotations(content) {
|
|
88
|
+
const eventLevelAnnotationsMapping = {
|
|
89
|
+
'x-sap-event-spec-version': 'EventSpecVersion', //expected: string
|
|
90
|
+
'x-sap-event-source': 'EventSource', //expected: string
|
|
91
|
+
'x-sap-event-source-parameters': 'EventSourceParams', //expected: object
|
|
92
|
+
'x-sap-event-characteristics': 'EventCharacteristics', //expected: object
|
|
93
|
+
'x-sap-stateInfo': 'EventStateInfo', //expected: object
|
|
94
|
+
'x-sap-event-version': 'EventSchemaVersion' //expected: string
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const annotations = this.getAnnotations(content, eventLevelAnnotationsMapping);
|
|
98
|
+
|
|
99
|
+
return annotations;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns validity of the annotation value. Here the parameter
|
|
104
|
+
* content is the annotation name and value of the annotation.
|
|
105
|
+
* @returns true/false depending on the annotation value.
|
|
106
|
+
*/
|
|
107
|
+
isValidAnnotationValue(annotation, value) {
|
|
108
|
+
const objectTypeAnnotations = ['StateInfo', 'EventSourceParams', 'EventCharacteristics', 'EventStateInfo'];
|
|
109
|
+
// if the value is an array or null
|
|
110
|
+
if (Array.isArray(value) || value === null) return false;
|
|
111
|
+
|
|
112
|
+
if (objectTypeAnnotations.includes(annotation) && (value instanceof Object)) {
|
|
113
|
+
return true;
|
|
114
|
+
} else if ((!objectTypeAnnotations.includes(annotation)) && (typeof value === 'string')) {
|
|
115
|
+
return true;
|
|
116
|
+
} else {
|
|
117
|
+
console.warn(`WARNING: Annotations which are not following the expected type are omitted.`);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns annotations for an event/service. Here the parameter content
|
|
124
|
+
* is event/service content along with the corresponding mapping.
|
|
125
|
+
* @returns an object with the annotations as key value pairs.
|
|
126
|
+
*/
|
|
127
|
+
getAnnotations(content, annotationsMapping) {
|
|
128
|
+
const annotations = {};
|
|
129
|
+
|
|
130
|
+
for (const [key, value] of Object.entries(content)) {
|
|
131
|
+
// case: known annotation
|
|
132
|
+
if (annotationsMapping[key] && this.isValidAnnotationValue(annotationsMapping[key], value)) {
|
|
133
|
+
annotations['@AsyncAPI.' + annotationsMapping[key]] = value;
|
|
134
|
+
}
|
|
135
|
+
// case: unkown annotation
|
|
136
|
+
else if (key.startsWith('x-sap-')) {
|
|
137
|
+
if (!annotations['@AsyncAPI.Extensions']) {
|
|
138
|
+
annotations['@AsyncAPI.Extensions'] = {};
|
|
139
|
+
}
|
|
140
|
+
annotations['@AsyncAPI.Extensions'][key] = value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return annotations;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns the referred schema. Here the content is the asyncapi
|
|
149
|
+
* document and payloadObject is the reference path
|
|
150
|
+
* @returns an object with the referred schema.
|
|
151
|
+
*/
|
|
152
|
+
resolveRef(content, payloadObject) {
|
|
153
|
+
let schema = {};
|
|
154
|
+
|
|
155
|
+
if (payloadObject['$ref']) {
|
|
156
|
+
const pathArray = payloadObject['$ref'].split('/');
|
|
157
|
+
pathArray.forEach((value) => {
|
|
158
|
+
if (value === '#') schema = content;
|
|
159
|
+
else if (schema[value]) schema = schema[value];
|
|
160
|
+
// in case path doesn't exist in the file
|
|
161
|
+
else schema = {};
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!Object.keys(schema).length) {
|
|
165
|
+
console.warn(`WARNING: Unable to resolve ref path: ` + payloadObject['$ref']);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (payloadObject instanceof Object) {
|
|
169
|
+
// in case ref is not there and schema object is there
|
|
170
|
+
schema = payloadObject;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return schema;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns the properties and types generated, related to an event.
|
|
178
|
+
* @returns a properties object and a types object.
|
|
179
|
+
*/
|
|
180
|
+
getPropertiesAndTypes(eventName, content) {
|
|
181
|
+
const properties = {};
|
|
182
|
+
let types = {};
|
|
183
|
+
|
|
184
|
+
if (content.properties) {
|
|
185
|
+
for (const [key, value] of Object.entries(content.properties)) {
|
|
186
|
+
// will contain type objects generated for a property
|
|
187
|
+
let referredTypes = {};
|
|
188
|
+
let isRequired = false;
|
|
189
|
+
if (content['required']?.includes(key)) isRequired = true;
|
|
190
|
+
[properties[key], referredTypes] = this.parseProperty(eventName, key, value, isRequired);
|
|
191
|
+
// collecting all the types
|
|
192
|
+
types = { ...types, ...referredTypes };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [properties, types];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Returns the processed objects for property and type.
|
|
200
|
+
* @returns property object and type object.
|
|
201
|
+
*/
|
|
202
|
+
parseProperty(eventName, propertyName, propertyContent, isRequired) {
|
|
203
|
+
let property = {};
|
|
204
|
+
let propertyAttributes = {};
|
|
205
|
+
let types = {};
|
|
206
|
+
|
|
207
|
+
if (isRequired === true) {
|
|
208
|
+
property['@mandatory'] = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let type = this.getTypeFromArray(propertyContent.type);
|
|
212
|
+
|
|
213
|
+
// case where type is defined
|
|
214
|
+
if (type && (type === 'object' || type === 'array')) {
|
|
215
|
+
// for generation of type: event name + property name
|
|
216
|
+
propertyName = propertyName[0].toUpperCase() + propertyName.substring(1);
|
|
217
|
+
let typeName = eventName + "." + propertyName;
|
|
218
|
+
// generate object for the introduced type
|
|
219
|
+
const elements = this.getTypeElements(propertyContent);
|
|
220
|
+
types[typeName] = { 'kind': 'type', ...elements };
|
|
221
|
+
// referring to the generated type from the property
|
|
222
|
+
propertyAttributes = { 'type': typeName };
|
|
223
|
+
} else {
|
|
224
|
+
propertyAttributes = this.getPropertyObject(propertyContent);
|
|
225
|
+
}
|
|
226
|
+
property = { ...property, ...propertyAttributes };
|
|
227
|
+
|
|
228
|
+
return [property, types];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns generated type for the property.
|
|
233
|
+
* @returns an object for a type.
|
|
234
|
+
*/
|
|
235
|
+
getTypeElements(propertyContent) {
|
|
236
|
+
let properties = {};
|
|
237
|
+
let type = this.getTypeFromArray(propertyContent.type);
|
|
238
|
+
|
|
239
|
+
// for nested object
|
|
240
|
+
if (type === 'object') {
|
|
241
|
+
for (const [key, value] of Object.entries(propertyContent.properties)) {
|
|
242
|
+
properties[key] = this.getTypeElements(value);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// for nested array
|
|
246
|
+
else if (type === 'array') {
|
|
247
|
+
properties.items = this.getTypeElements(propertyContent.items);
|
|
248
|
+
return properties;
|
|
249
|
+
}
|
|
250
|
+
// base data type mapping
|
|
251
|
+
else {
|
|
252
|
+
return this.getPropertyObject(propertyContent);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { elements: properties };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Returns type for the property from the AsyncAPI Document.
|
|
260
|
+
* @returns the value of type.
|
|
261
|
+
*/
|
|
262
|
+
getTypeFromArray(typeValue) {
|
|
263
|
+
let type;
|
|
264
|
+
// if type is an array
|
|
265
|
+
if (Array.isArray(typeValue)) {
|
|
266
|
+
// one of the value will be null
|
|
267
|
+
type = typeValue[0] === 'null' ? typeValue[1] : typeValue[0];
|
|
268
|
+
} else {
|
|
269
|
+
type = typeValue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return type;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Returns generated object for a single property.
|
|
277
|
+
* @returns an object for a property.
|
|
278
|
+
*/
|
|
279
|
+
getPropertyObject(propertyContent) {
|
|
280
|
+
const property = {};
|
|
281
|
+
const inputType = this.getTypeFromArray(propertyContent.type);
|
|
282
|
+
const cdsType = this.getDataType(inputType, propertyContent.format);
|
|
283
|
+
|
|
284
|
+
property['type'] = cdsType;
|
|
285
|
+
|
|
286
|
+
// case: cds type is decimal
|
|
287
|
+
if (cdsType === 'cds.Decimal') {
|
|
288
|
+
if (propertyContent['x-sap-precision']) property.precision = propertyContent['x-sap-precision'];
|
|
289
|
+
if (propertyContent['x-sap-scale']) property.scale = propertyContent['x-sap-scale'];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (propertyContent['default']) {
|
|
293
|
+
property['default'] = { 'val': propertyContent['default'] };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (propertyContent['enum']) {
|
|
297
|
+
property['enum'] = this.getEnumObject(propertyContent['enum']);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return property;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Takes the enums from the AsyncAPI Document.
|
|
305
|
+
* @returns the property object of enums.
|
|
306
|
+
*/
|
|
307
|
+
getEnumObject(enumArray) {
|
|
308
|
+
const enumProperty = {};
|
|
309
|
+
|
|
310
|
+
enumArray.forEach(val => {
|
|
311
|
+
enumProperty[val] = {};
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return enumProperty;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Takes the type and format from the document and returns the corresponding cds type.
|
|
319
|
+
* @returns the corresponding cds type.
|
|
320
|
+
*/
|
|
321
|
+
getDataType(type, format = null) {
|
|
322
|
+
const asyncapiToCdsMapping = {
|
|
323
|
+
'number': 'cds.Double',
|
|
324
|
+
'boolean': 'cds.Boolean',
|
|
325
|
+
'integer': 'cds.Integer'
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (type === 'string') {
|
|
329
|
+
switch (format) {
|
|
330
|
+
case 'int64':
|
|
331
|
+
return 'cds.Integer64';
|
|
332
|
+
case 'decimal':
|
|
333
|
+
return 'cds.Decimal';
|
|
334
|
+
case 'uuid':
|
|
335
|
+
return 'cds.UUID';
|
|
336
|
+
case 'date':
|
|
337
|
+
return 'cds.Date';
|
|
338
|
+
case 'partial-time':
|
|
339
|
+
return 'cds.Time';
|
|
340
|
+
case 'date-time':
|
|
341
|
+
return 'cds.Timestamp';
|
|
342
|
+
default:
|
|
343
|
+
return 'cds.LargeString';
|
|
344
|
+
}
|
|
345
|
+
} else if (asyncapiToCdsMapping[type]) {
|
|
346
|
+
return asyncapiToCdsMapping[type];
|
|
347
|
+
} else {
|
|
348
|
+
throw new Error(messages.UNRESOLVED_TYPE + `'${type}'`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const messages = require("../message").getMessages();
|
|
2
|
+
|
|
3
|
+
function validateAsyncAPI(content) {
|
|
4
|
+
if ('channels' in content && 'components' in content && 'messages' in content.components) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
throw new Error(messages.INVALID_ASYNCAPI_FILE);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const typeRegex = new RegExp(
|
|
12
|
+
'^(?<namespace>(?:[a-z][a-z0-9]*)(?:[.][a-z][a-z0-9]*)+)[.](?<businessObject>[a-zA-Z0-9]+)[.](?<operation>[a-zA-Z0-9]+)([.](?<version>v(?:[0-9]|[1-9][0-9]*)))?$'
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
validateAsyncAPI,
|
|
17
|
+
typeRegex
|
|
18
|
+
}
|
package/lib/import/common.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-prototype-builtins */
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
let convert = require('xml-js');
|
|
@@ -18,17 +19,6 @@ let optionsMapping = {
|
|
|
18
19
|
let apiOptions = ['includeNamespaces', 'keepNamespace'];
|
|
19
20
|
let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force'];
|
|
20
21
|
|
|
21
|
-
function _isJson(edmx) {
|
|
22
|
-
let isJson = false;
|
|
23
|
-
try {
|
|
24
|
-
JSON.parse(edmx);
|
|
25
|
-
isJson = true;
|
|
26
|
-
} catch (err) {
|
|
27
|
-
isJson = false;
|
|
28
|
-
}
|
|
29
|
-
return isJson;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
22
|
async function generateEDMX2JSON(edmx) {
|
|
33
23
|
let cjson = convert.xml2json(edmx, {
|
|
34
24
|
compact: true,
|
|
@@ -49,71 +39,34 @@ async function _isValidXML(edmx) {
|
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
async function _getODataVersion(edmx) {
|
|
52
|
-
let oDataVersion;
|
|
53
|
-
let jsonObj;
|
|
54
|
-
let dataServices;
|
|
55
|
-
let dataServicesAttributes;
|
|
56
|
-
// Check OData version
|
|
57
|
-
if (_isJson(edmx)) {
|
|
58
|
-
return '';
|
|
59
|
-
}
|
|
60
|
-
jsonObj = await generateEDMX2JSON(edmx);
|
|
61
|
-
try {
|
|
62
|
-
dataServices = jsonObj['edmx:Edmx']['edmx:DataServices'];
|
|
63
|
-
dataServicesAttributes = dataServices._attributes;
|
|
64
|
-
} catch (err) {
|
|
65
|
-
oDataVersion = '';
|
|
66
|
-
}
|
|
67
|
-
if (dataServicesAttributes) {
|
|
68
|
-
oDataVersion = dataServicesAttributes['m:DataServiceVersion'];
|
|
69
|
-
} else {
|
|
70
|
-
// V4 edmx
|
|
71
|
-
try {
|
|
72
|
-
oDataVersion = jsonObj['edmx:Edmx']._attributes.Version;
|
|
73
|
-
} catch (err) {
|
|
74
|
-
oDataVersion = '';
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return oDataVersion;
|
|
78
|
-
}
|
|
42
|
+
let oDataVersion = "";
|
|
79
43
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (version === '1.0' || version === '2.0') {
|
|
85
|
-
isV2 = true;
|
|
44
|
+
let checkForV2 = edmx.match(/m:DataServiceVersion="[^"]+"/);
|
|
45
|
+
if (checkForV2) {
|
|
46
|
+
oDataVersion = checkForV2[0].split('"')[1];
|
|
47
|
+
return oDataVersion;
|
|
86
48
|
}
|
|
87
|
-
return isV2;
|
|
88
|
-
}
|
|
89
49
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (_isJson(fileInputStream)) {
|
|
95
|
-
edmj = JSON.parse(fileInputStream);
|
|
96
|
-
version = edmj.$Version;
|
|
97
|
-
} else {
|
|
98
|
-
version = await _getODataVersion(fileInputStream);
|
|
50
|
+
let edmxMatching = edmx.match(/<edmx:Edmx[^>]+"/);
|
|
51
|
+
if (edmxMatching) {
|
|
52
|
+
let checkForV4 = edmxMatching[0].match(/Version="[^"]+"/);
|
|
53
|
+
oDataVersion = checkForV4[0].split('"')[1];
|
|
99
54
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return isV4;
|
|
55
|
+
|
|
56
|
+
return oDataVersion;
|
|
104
57
|
}
|
|
105
58
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// input can be JSON or XML
|
|
109
|
-
// if xml, find the version
|
|
110
|
-
// if json, find the version
|
|
59
|
+
// method to find the version, input is in XML format
|
|
60
|
+
async function getVersion(fileInputStream){
|
|
111
61
|
let oDataVersion;
|
|
112
|
-
|
|
62
|
+
const version = await _getODataVersion(fileInputStream);
|
|
63
|
+
|
|
64
|
+
if (version === '1.0' || version === '2.0') {
|
|
113
65
|
oDataVersion = 'V2';
|
|
114
|
-
} else if (
|
|
66
|
+
} else if (version === '4.0' || version === "4.01") {
|
|
115
67
|
oDataVersion = 'V4';
|
|
116
68
|
}
|
|
69
|
+
|
|
117
70
|
return oDataVersion;
|
|
118
71
|
}
|
|
119
72
|
|
|
@@ -402,9 +355,10 @@ async function identifyFile(filepath) {
|
|
|
402
355
|
const src = await getContentFromFile(filepath);
|
|
403
356
|
//for openapi
|
|
404
357
|
let jsonSrc = JSON.parse(src);
|
|
405
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
406
358
|
if (jsonSrc.hasOwnProperty('openapi') || jsonSrc.hasOwnProperty('swagger')) {
|
|
407
359
|
return "openapi";
|
|
360
|
+
} else if (jsonSrc.hasOwnProperty('asyncapi')){
|
|
361
|
+
return "asyncapi";
|
|
408
362
|
}
|
|
409
363
|
} else
|
|
410
364
|
return extension;
|
package/lib/import/importapi.js
CHANGED
|
@@ -3,12 +3,14 @@ let { path } = cds.utils;
|
|
|
3
3
|
let common = require('./common');
|
|
4
4
|
let odata = require('./odata');
|
|
5
5
|
let openapi = require('./openapi');
|
|
6
|
+
let asyncapi = require('./asyncapi');
|
|
6
7
|
let messages = require('./message').getMessages();
|
|
7
8
|
|
|
8
9
|
module.exports = Object.assign(cds_import, {
|
|
9
10
|
from: cds.lazified({
|
|
10
11
|
edmx: cds_import_from_edmx,
|
|
11
|
-
openapi: cds_import_from_openapi
|
|
12
|
+
openapi: cds_import_from_openapi,
|
|
13
|
+
asyncapi: cds_import_from_asyncapi
|
|
12
14
|
})
|
|
13
15
|
});
|
|
14
16
|
|
|
@@ -35,6 +37,9 @@ async function cds_import(filepath, options = { keepNamespace: false }) {
|
|
|
35
37
|
case "openapi":
|
|
36
38
|
csn = await cds_import_from_openapi(filepath, options);
|
|
37
39
|
break;
|
|
40
|
+
case "asyncapi":
|
|
41
|
+
csn = await cds_import_from_asyncapi(filepath, options);
|
|
42
|
+
break;
|
|
38
43
|
default:
|
|
39
44
|
throw new Error(messages.INVALID_INPUT_FILE);
|
|
40
45
|
}
|
|
@@ -63,12 +68,13 @@ async function cds_import_from_edmx(filepath, options = { keepNamespace: false }
|
|
|
63
68
|
common.readEnvVariables(odataOptions, false);
|
|
64
69
|
}
|
|
65
70
|
// generates csn
|
|
71
|
+
odataOptions.odataVersion = await common.getVersion(src);
|
|
66
72
|
odataOptions.include_all_namespaces = false;
|
|
67
73
|
odataOptions.namespaces = [];
|
|
68
74
|
if (odataOptions.include_namespaces) odata.getNamespaces(odataOptions);
|
|
69
75
|
let csn = await odata.edmx2csn(src, !odataOptions.keep_namespace && filepath, odataOptions);
|
|
70
76
|
//reading the file version
|
|
71
|
-
options.
|
|
77
|
+
options.odataVersion = odataOptions.odataVersion;
|
|
72
78
|
// return the csn
|
|
73
79
|
return csn;
|
|
74
80
|
}
|
|
@@ -86,4 +92,19 @@ async function cds_import_from_openapi(filepath, options = {}) {
|
|
|
86
92
|
let csn = openapi.openAPI2csn(src);
|
|
87
93
|
options.inputFileVersion = 'rest';
|
|
88
94
|
return csn;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* This is the import API function to generate CSN from asyncapi files.
|
|
99
|
+
* @param {string} filepath
|
|
100
|
+
* - a single valid filepath
|
|
101
|
+
* @returns {object} CSN
|
|
102
|
+
*/
|
|
103
|
+
async function cds_import_from_asyncapi(filepath, options = {}) {
|
|
104
|
+
// validates the input file
|
|
105
|
+
filepath = await common.isValidInputFile(path.resolve(process.cwd(), filepath));
|
|
106
|
+
const src = await common.getContentFromFile(filepath);
|
|
107
|
+
let csn = asyncapi.asyncapi2csn(src);
|
|
108
|
+
options.inputFileVersion = 'odata';
|
|
109
|
+
return csn;
|
|
89
110
|
}
|
package/lib/import/message.js
CHANGED
|
@@ -16,9 +16,19 @@ const messages = {
|
|
|
16
16
|
MISSING_ASSOCIATION: 'Association is missing in the input file provided. Please check the edmx file.',
|
|
17
17
|
COLLECTION_IN_KEY: 'Key Property of an Entity cannot be of the type Collection.',
|
|
18
18
|
INVALID_OPENAPI_FILE: 'The OpenAPI file is not valid. Specify the correct OpenAPI file.',
|
|
19
|
+
INVALID_ASYNCAPI_FILE: 'The AsyncAPI file is not valid. Specify the correct AsyncAPI file.',
|
|
20
|
+
INVALID_MESSAGE_NAME: 'The message name is not valid.',
|
|
19
21
|
UNRESOLVED_TYPE: 'Could not resolve the type: ',
|
|
20
22
|
NO_RETURN_TYPE: 'No ReturnType found in Function: ',
|
|
21
|
-
INVALID_ENUM_TYPE: 'The Underlying type provided is not supported for the EnumType: '
|
|
23
|
+
INVALID_ENUM_TYPE: 'The Underlying type provided is not supported for the EnumType: ',
|
|
24
|
+
UNBOUNDED_ACTION_COLLISION: 'Unbounded Action does not support collision. Check the Action ',
|
|
25
|
+
INVALID_BOUNDED_PARAM_TYPE: 'Parameter Type of Bounded Action/Function should not refer to an Entity in another Schema.',
|
|
26
|
+
BOUNDED_ACTION_COLLISION: 'For Bounded Action, combination of action name and binding parameter type should be unique.',
|
|
27
|
+
STREAM_NO_COLLECTION: `'Edm.Stream' cannot be of Collection type.`,
|
|
28
|
+
UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
|
|
29
|
+
UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
|
|
30
|
+
INVALID_DATATYPE: `Invalid Data Type `,
|
|
31
|
+
ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currenlty not supported. Please check the element name: '
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
function getMessages() {
|
|
@@ -53,6 +53,7 @@ function _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace) {
|
|
|
53
53
|
|
|
54
54
|
async function edmx2csn(edmx, filename, context) {
|
|
55
55
|
let namespace;
|
|
56
|
+
// we might get aliased namespace value here
|
|
56
57
|
const result = edmx.match(/EntityType="([^"]+)/);
|
|
57
58
|
const multipleSchemas = /(\s*<\/Schema>\s*<Schema [^>]*>)\s*/m;
|
|
58
59
|
if (result) {
|
|
@@ -67,13 +68,23 @@ async function edmx2csn(edmx, filename, context) {
|
|
|
67
68
|
schema = schema[0].match(/Namespace="[^"]+"/);
|
|
68
69
|
namespace = schema[0].slice(schema[0].lastIndexOf('Namespace') + 11, schema[0].lastIndexOf('"'));
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
// this hack is only used for OData V2, for V4 we support multiple schema scenario
|
|
72
|
+
if (context.odataVersion === 'V2' && multipleSchemas.test(edmx)) {
|
|
71
73
|
edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
|
|
72
74
|
}
|
|
73
75
|
let csn = await _generateCSN(edmx, false, true, context);
|
|
74
76
|
const regex = /^[\p{Letter}\p{Nl}_]{1}[_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu
|
|
75
77
|
// Regex checks for this condition “A-B” must start with a letter or underscore, followed by at most 127 letters, underscores or digits in the fileName
|
|
76
78
|
if (filename) {
|
|
79
|
+
/**
|
|
80
|
+
* V4: <Schema Namespace="Full.Schema.Name" xmlns="http://docs.oasis-open.org/odata/ns/edm" Alias="aliasedValue">
|
|
81
|
+
* V2: <Schema Namespace="Full.Schema.Name" xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Alias="aliasedValue">
|
|
82
|
+
*
|
|
83
|
+
* context.schemaNamespace is set to correct namespace value by the respective parsers
|
|
84
|
+
*/
|
|
85
|
+
if (context.schemaNamespace) {
|
|
86
|
+
namespace = context.schemaNamespace;
|
|
87
|
+
}
|
|
77
88
|
// Replace namespace by given filename in the csn file instead of edmx file
|
|
78
89
|
const re = RegExp(`([("/])${namespace}([/").])`, 'g');
|
|
79
90
|
let name = path.parse(filename).name;
|
|
@@ -89,16 +100,11 @@ async function edmx2csn(edmx, filename, context) {
|
|
|
89
100
|
|
|
90
101
|
async function _generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
|
|
91
102
|
let csn;
|
|
92
|
-
let odataVersion;
|
|
93
103
|
let edmx2jsonModel;
|
|
94
104
|
let defaultNamespaces;
|
|
95
105
|
let namespaces = [];
|
|
96
106
|
if (context.namespaces) namespaces.push(...(context.namespaces));
|
|
97
|
-
odataVersion
|
|
98
|
-
if (odataVersion === "V2") {
|
|
99
|
-
if (context) {
|
|
100
|
-
context.inputFileVersion = "V2";
|
|
101
|
-
}
|
|
107
|
+
if (context.odataVersion === "V2") {
|
|
102
108
|
// default namespaces to be present
|
|
103
109
|
if (!context.include_all_namespaces) {
|
|
104
110
|
defaultNamespaces = ['edmx', 'sap', 'm'];
|
|
@@ -113,10 +119,7 @@ async function _generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context)
|
|
|
113
119
|
else reject("Error Occurred");
|
|
114
120
|
});
|
|
115
121
|
}
|
|
116
|
-
else if (odataVersion === 'V4') {
|
|
117
|
-
if (context) {
|
|
118
|
-
context.inputFileVersion = "V4";
|
|
119
|
-
}
|
|
122
|
+
else if (context.odataVersion === 'V4') {
|
|
120
123
|
// default namespaces to be present
|
|
121
124
|
if (!context.include_all_namespaces) {
|
|
122
125
|
defaultNamespaces = ['edmx'];
|