@sap/cds-dk 10.2.0 → 10.2.1
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 +13 -0
- package/bin/bind.js +5 -4
- package/bin/completion/scripts/cds.fish +4 -3
- package/bin/completion/scripts/cds.ps1 +4 -4
- package/bin/completion/scripts/cds.sh +4 -4
- package/bin/completion/scripts/cds.zsh +4 -4
- package/bin/debug.js +3 -3
- package/bin/deploy/index.js +2 -2
- package/bin/deploy/to-hana/hana.js +1 -1
- package/bin/deploy/to-hana/hdiDeployUtil.js +25 -14
- package/bin/up.js +3 -3
- package/bin/version.js +1 -1
- package/bin/watch/vite-watch.js +12 -6
- package/lib/bind/shared.js +1 -1
- package/lib/init/index.js +1 -0
- package/lib/init/projectReader.js +3 -2
- package/lib/init/template/approuter/files/values.yaml.hbs +0 -9
- package/lib/init/template/approuter/files/xs-app.json.hbs +0 -22
- package/lib/init/template/approuter/index.js +8 -9
- package/lib/init/template/html5-repo/index.js +12 -0
- package/lib/init/template/react/files/vite.config.{{extension}}.hbs +2 -3
- package/lib/init/template/react/index.js +1 -3
- package/lib/init/template/sample/files/app/index.html.hbs +18 -0
- package/lib/init/template/sample/index.js +1 -0
- package/lib/init/template/vue/files/vite.config.{{extension}}.hbs +2 -3
- package/lib/init/template/vue/index.js +1 -3
- package/lib/mtx/push.js +7 -1
- package/node_modules/@cap-js/asyncapi/README.md +4 -14
- package/node_modules/@cap-js/asyncapi/cds-plugin.js +12 -0
- package/node_modules/@cap-js/asyncapi/index.js +3 -2
- package/node_modules/@cap-js/asyncapi/lib/compile/components/messages/index.js +4 -2
- package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/definition.js +11 -1
- package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/utils.js +16 -0
- package/node_modules/@cap-js/asyncapi/lib/compile/utils.js +4 -3
- package/node_modules/@cap-js/asyncapi/lib/import/importAsyncAPI.js +34 -0
- package/node_modules/@cap-js/asyncapi/lib/import/index.js +18 -0
- package/node_modules/@cap-js/asyncapi/lib/import/message.js +9 -0
- package/node_modules/@cap-js/asyncapi/lib/import/parserContext.js +368 -0
- package/node_modules/@cap-js/asyncapi/lib/import/utils.js +85 -0
- package/node_modules/@cap-js/asyncapi/package.json +89 -1
- package/node_modules/@sap/cds/CHANGELOG.md +10 -0
- package/node_modules/@sap/cds/app/index.js +1 -1
- package/node_modules/@sap/cds/lib/env/schemas/cds-package.js +0 -2
- package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +3 -3
- package/node_modules/@sap/cds/lib/env/schemas/index.js +4 -4
- package/node_modules/@sap/cds/lib/utils/cds-utils.js +21 -8
- package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/connections.js +45 -39
- package/node_modules/@sap/cds/libx/odata/ODataAdapter.js +1 -1
- package/node_modules/@sap/cds/libx/queue/TaskRunner.js +22 -5
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +8 -0
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +57 -41
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +27 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +31 -25
- package/node_modules/body-parser/README.md +17 -13
- package/node_modules/body-parser/lib/read.js +74 -57
- package/node_modules/body-parser/lib/types/json.js +11 -11
- package/node_modules/body-parser/lib/types/urlencoded.js +5 -5
- package/node_modules/body-parser/lib/utils.js +0 -15
- package/node_modules/body-parser/package.json +7 -12
- package/node_modules/encodeurl/README.md +14 -0
- package/node_modules/encodeurl/index.js +5 -10
- package/node_modules/encodeurl/package.json +10 -6
- package/node_modules/on-finished/index.js +14 -2
- package/node_modules/on-finished/package.json +11 -7
- package/node_modules/qs/.editorconfig +1 -1
- package/node_modules/qs/CHANGELOG.md +87 -22
- package/node_modules/qs/README.md +63 -10
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/eslint.config.mjs +61 -50
- package/node_modules/qs/lib/formats.js +7 -2
- package/node_modules/qs/lib/parse.js +29 -10
- package/node_modules/qs/lib/stringify.js +5 -3
- package/node_modules/qs/lib/utils.js +28 -9
- package/node_modules/qs/package.json +6 -6
- package/node_modules/qs/test/parse.js +335 -2
- package/node_modules/qs/test/stringify.js +222 -0
- package/node_modules/qs/test/utils.js +61 -3
- package/node_modules/semver/classes/range.js +2 -2
- package/node_modules/semver/internal/constants.js +30 -0
- package/node_modules/semver/internal/trim-comparator-whitespace.js +438 -0
- package/node_modules/semver/package.json +1 -1
- package/node_modules/ws/README.md +21 -29
- package/node_modules/ws/lib/buffer-util.js +13 -2
- package/node_modules/ws/lib/sender.js +4 -1
- package/node_modules/ws/lib/websocket.js +8 -3
- package/node_modules/ws/package.json +1 -1
- package/package.json +1 -1
- package/node_modules/on-finished/HISTORY.md +0 -98
package/lib/mtx/push.js
CHANGED
|
@@ -8,6 +8,7 @@ const buildSystem = require('../build')
|
|
|
8
8
|
|
|
9
9
|
const { buildResponseError, buildNetworkError, mtxFetch } = require('./util/request');
|
|
10
10
|
const { getMessage } = require('./util/logging');
|
|
11
|
+
const { bold } = require('../util/term');
|
|
11
12
|
const { schemaRegex } = require('./util/urls');
|
|
12
13
|
|
|
13
14
|
const JOB_STATUS = {
|
|
@@ -58,11 +59,16 @@ module.exports = class Push {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
static getExtensionName(projectFolder) {
|
|
62
|
+
let name;
|
|
61
63
|
try {
|
|
62
|
-
|
|
64
|
+
name = require(join(projectFolder, 'package.json')).name;
|
|
63
65
|
} catch (error) {
|
|
64
66
|
throw getMessage(`Extension project at ${projectFolder} is missing package.json file`, { error, command: 'push' });
|
|
65
67
|
}
|
|
68
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
69
|
+
throw getMessage(`Extension project at ${projectFolder} requires a non-empty ${bold('name')} in package.json`, { command: 'push' });
|
|
70
|
+
}
|
|
71
|
+
return name;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
static async pushTgz(params, tgz, extensionName) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[](https://api.reuse.software/info/github.com/cap-js/asyncapi)
|
|
2
2
|
|
|
3
|
-
# AsyncAPI
|
|
3
|
+
# AsyncAPI
|
|
4
4
|
|
|
5
5
|
## About this project
|
|
6
6
|
|
|
@@ -18,20 +18,10 @@ The `@cap-js/asyncapi` is a package that provides support for AsyncAPI document
|
|
|
18
18
|
### Installation
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
|
-
|
|
21
|
+
npm add -D @cap-js/asyncapi
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```js
|
|
27
|
-
const cds = require('@sap/cds')
|
|
28
|
-
const { compile } = require('@cap-js/asyncapi')
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
```js
|
|
32
|
-
const csn = await cds.load(cds.env.folders.srv)
|
|
33
|
-
const asyncapiDocument = compile(csn)
|
|
34
|
-
```
|
|
24
|
+
See the [AsyncAPI guide](https://cap.cloud.sap/docs/guides/protocols/asyncapi) for the full documentation.
|
|
35
25
|
|
|
36
26
|
## Contributing
|
|
37
27
|
|
|
@@ -43,4 +33,4 @@ We as members, contributors, and leaders pledge to make participation in our com
|
|
|
43
33
|
|
|
44
34
|
## Licensing
|
|
45
35
|
|
|
46
|
-
Copyright
|
|
36
|
+
Copyright 2026 SAP SE or an SAP affiliate company and contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/cap-js/asyncapi).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const cds = require('@sap/cds')
|
|
2
|
+
const { compile, import: asyncapi } = require('./index')
|
|
3
|
+
|
|
4
|
+
// monkey-patch cds.compile.to and cds.import.from directly, same pattern as cds-dk's lazy getters
|
|
5
|
+
if (cds.compile?.to) cds.compile.to.asyncapi = compile
|
|
6
|
+
|
|
7
|
+
if (cds.import?.from) cds.import.from.asyncapi = async function (filepath, options = {}) {
|
|
8
|
+
const src = await cds.utils.read(filepath, 'utf-8')
|
|
9
|
+
const csn = asyncapi.asyncapi2csn(src)
|
|
10
|
+
options.inputFileKind = 'odata'
|
|
11
|
+
return csn
|
|
12
|
+
}
|
|
@@ -125,6 +125,7 @@ function eventToMessage(definition, name, presets, i18nBundle) {
|
|
|
125
125
|
|
|
126
126
|
let messageObj = {
|
|
127
127
|
name,
|
|
128
|
+
'x-service-name': definition._service.name,
|
|
128
129
|
headers: {
|
|
129
130
|
type: 'object',
|
|
130
131
|
properties: getHeaderProperties(name)
|
|
@@ -157,8 +158,9 @@ function entriesToMessages(definitions, events, presets, i18nBundle) {
|
|
|
157
158
|
const messages = {}
|
|
158
159
|
|
|
159
160
|
events.forEach(event => {
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
const definition = definitions[event]
|
|
162
|
+
let name = getEventName(event, definition)
|
|
163
|
+
messages[name] = eventToMessage(definition, name, presets, i18nBundle)
|
|
162
164
|
})
|
|
163
165
|
|
|
164
166
|
return messages
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { addEnum } = require('./enum')
|
|
2
2
|
const schemaUtils = require('./utils')
|
|
3
|
+
const { makeNullable } = require('./utils')
|
|
3
4
|
const { cacheAndReturn, getCache } = require('./cache')
|
|
4
5
|
const { getOwnValue } = require('../../utils')
|
|
5
6
|
const { toLocalizedArray } = require('./localized')
|
|
@@ -30,6 +31,7 @@ function elementsToSchema(schemaCache, definitions, entry, nullable, i18nBundle)
|
|
|
30
31
|
const object = schemaUtils.getBaseTypeStructure('object')
|
|
31
32
|
for (const [name, element] of Object.entries(entry.elements)) {
|
|
32
33
|
if (getOwnValue(element, 'key')
|
|
34
|
+
|| getOwnValue(element, 'notNull')
|
|
33
35
|
|| getOwnValue(element, '@mandatory')
|
|
34
36
|
|| (getOwnValue(element, '@Common.FieldControl') && getOwnValue(element['@Common.FieldControl'], '#') === 'Mandatory')
|
|
35
37
|
) {
|
|
@@ -44,7 +46,15 @@ function elementsToSchema(schemaCache, definitions, entry, nullable, i18nBundle)
|
|
|
44
46
|
object.properties = {}
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
let propSchema = addAnnotations(element, elementToSchema(schemaCache, definitions, element, i18nBundle), i18nBundle)
|
|
50
|
+
if (!getOwnValue(element, 'notNull')
|
|
51
|
+
&& !getOwnValue(element, 'key')
|
|
52
|
+
&& !getOwnValue(element, '@mandatory')
|
|
53
|
+
&& !(getOwnValue(element, '@Common.FieldControl') && getOwnValue(element['@Common.FieldControl'], '#') === 'Mandatory')
|
|
54
|
+
) {
|
|
55
|
+
propSchema = makeNullable(propSchema)
|
|
56
|
+
}
|
|
57
|
+
object.properties[name] = propSchema
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
if (required.length) {
|
|
@@ -88,10 +88,26 @@ function isRefEntry(entry) {
|
|
|
88
88
|
return typeof type === 'object' && !!type && 'ref' in type && Array.isArray(type.ref)
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function makeNullable(schema) {
|
|
92
|
+
const type = schema.type
|
|
93
|
+
const nullableSchema = { ...schema }
|
|
94
|
+
|
|
95
|
+
if (type && !Array.isArray(type)) {
|
|
96
|
+
nullableSchema.type = [type, 'null']
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(nullableSchema.enum) && !nullableSchema.enum.includes(null)) {
|
|
100
|
+
nullableSchema.enum = [...nullableSchema.enum, null]
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return nullableSchema
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
module.exports = {
|
|
92
107
|
toString,
|
|
93
108
|
getBaseTypeStructure,
|
|
94
109
|
getFormatStructure,
|
|
110
|
+
makeNullable,
|
|
95
111
|
isAssociation,
|
|
96
112
|
isComposition,
|
|
97
113
|
isStructured,
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* @return {string} The event type name
|
|
6
6
|
*/
|
|
7
7
|
function getEventName(eventName, eventDefinition) {
|
|
8
|
-
|
|
9
8
|
if (eventDefinition['@AsyncAPI.EventType']) {
|
|
10
|
-
|
|
9
|
+
return eventDefinition['@AsyncAPI.EventType']
|
|
10
|
+
}
|
|
11
|
+
if (eventDefinition['@topic']) {
|
|
12
|
+
return eventDefinition['@topic']
|
|
11
13
|
}
|
|
12
|
-
|
|
13
14
|
return eventName
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const ParserContext = require('./parserContext')
|
|
2
|
+
const { join } = require('path')
|
|
3
|
+
const version = require(join(__dirname, '..', '..', 'package.json')).version
|
|
4
|
+
|
|
5
|
+
function importAsyncAPI(input) {
|
|
6
|
+
const context = new ParserContext(input)
|
|
7
|
+
return convertToCSN(context)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function convertToCSN(context) {
|
|
11
|
+
const csn = {
|
|
12
|
+
meta: {
|
|
13
|
+
creator: `@cap-js/asyncapi ${version}`
|
|
14
|
+
},
|
|
15
|
+
$version: '2.0',
|
|
16
|
+
definitions: {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Object.entries(context.services).forEach(([key, value]) => {
|
|
20
|
+
const events = value.events
|
|
21
|
+
const types = value.types
|
|
22
|
+
delete value.events
|
|
23
|
+
delete value.types
|
|
24
|
+
|
|
25
|
+
csn.definitions[key] = value
|
|
26
|
+
Object.assign(csn.definitions, events, types)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return csn
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
importAsyncAPI
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const { importAsyncAPI } = require('./importAsyncAPI')
|
|
2
|
+
const { validateAsyncAPI } = require('./utils')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The starting function for conversion of AsyncAPI document to csn
|
|
6
|
+
* @returns AsyncAPI document
|
|
7
|
+
*/
|
|
8
|
+
function asyncapi2csn(source) {
|
|
9
|
+
// validate asyncapi document
|
|
10
|
+
const content = JSON.parse(source)
|
|
11
|
+
validateAsyncAPI(content)
|
|
12
|
+
|
|
13
|
+
return importAsyncAPI(content)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
asyncapi2csn
|
|
18
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const errorMessages = {
|
|
4
|
+
INVALID_ASYNCAPI_FILE: 'The AsyncAPI file is not valid. Specify the correct AsyncAPI file.',
|
|
5
|
+
INVALID_MESSAGE_NAME: 'The message name is not valid.',
|
|
6
|
+
UNRESOLVED_TYPE: 'Could not resolve the type: ',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = errorMessages
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
const { parseMessageName } = require('./utils')
|
|
2
|
+
const messages = require('./message')
|
|
3
|
+
|
|
4
|
+
module.exports = class ParserContext {
|
|
5
|
+
/**
|
|
6
|
+
* @constructor
|
|
7
|
+
*/
|
|
8
|
+
constructor(content) {
|
|
9
|
+
this.services = this.getServicesFromContent(content)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getTopicName(eventName) {
|
|
13
|
+
return eventName.startsWith('ce/') ? eventName.substring(3) : eventName
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns services from the asyncapi json object along with the events and annotations.
|
|
18
|
+
* @returns an object with all the services.
|
|
19
|
+
*/
|
|
20
|
+
getServicesFromContent(content) {
|
|
21
|
+
const services = {}
|
|
22
|
+
const serviceAnnotations = this.getServiceAnnotations(content)
|
|
23
|
+
|
|
24
|
+
Object.entries(content.components.messages).forEach(([, value]) => {
|
|
25
|
+
let eventName
|
|
26
|
+
// Message name should be used to capture the event name
|
|
27
|
+
if (value.name) {
|
|
28
|
+
eventName = value.name
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(messages.INVALID_ASYNCAPI_FILE)
|
|
31
|
+
}
|
|
32
|
+
// uses regex to break event name in different components
|
|
33
|
+
// x-sap-cds-service-name is set by the CAP exporter and is the authoritative
|
|
34
|
+
// service key — use it directly to avoid parsing the message name string.
|
|
35
|
+
const service = value['x-service-name'] ?? parseMessageName(eventName, content['x-sap-application-namespace'])[1]
|
|
36
|
+
const event = eventName
|
|
37
|
+
|
|
38
|
+
// add service to services entry if not present
|
|
39
|
+
if (!(service in services)) {
|
|
40
|
+
services[service] = { 'kind': 'service', '@cds.external': 'true', ...serviceAnnotations, 'events': {}, 'types': {} }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// add event entry to service
|
|
44
|
+
const eventAnnotations = this.getEventAnnotations(value)
|
|
45
|
+
|
|
46
|
+
// add topic annotation
|
|
47
|
+
eventAnnotations['@topic'] = this.getTopicName(eventName)
|
|
48
|
+
let properties = {}
|
|
49
|
+
let types = {}
|
|
50
|
+
|
|
51
|
+
if (value.payload) {
|
|
52
|
+
// gets the schema being referred to in the event
|
|
53
|
+
const eventSchema = this.resolveRef(content, value.payload)
|
|
54
|
+
;[properties, types] = this.getPropertiesAndTypes(event, eventSchema)
|
|
55
|
+
}
|
|
56
|
+
services[service].types = types
|
|
57
|
+
services[service].events[event] = { 'kind': 'event', '@cds.external': 'true', ...eventAnnotations, elements: properties }
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return services
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns annotations of a particular service.
|
|
65
|
+
* @returns an object with all the annotations as key value pairs.
|
|
66
|
+
*/
|
|
67
|
+
getServiceAnnotations(content) {
|
|
68
|
+
const serviceAnnotationsMapping = {
|
|
69
|
+
'x-sap-stateInfo': 'StateInfo', // expected: object
|
|
70
|
+
'x-sap-shortText': 'ShortText' // expected: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const infoAnnotationsMapping = {
|
|
74
|
+
'title': 'Title', // expected: string
|
|
75
|
+
'version': 'SchemaVersion', // expected: string
|
|
76
|
+
'description': 'Description' // expected: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const annotations = this.getAnnotations(content, serviceAnnotationsMapping)
|
|
80
|
+
|
|
81
|
+
if (content.info) {
|
|
82
|
+
for (const [key, value] of Object.entries(infoAnnotationsMapping)) {
|
|
83
|
+
if (content.info[key] && !(content.info[key] instanceof Object)) {
|
|
84
|
+
// info values will always be in string format
|
|
85
|
+
annotations['@AsyncAPI.' + value] = content.info[key]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return annotations
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Returns annotations of a particular event. Here the parameter content is event content.
|
|
95
|
+
* @returns an object with all the annotations as key value pairs.
|
|
96
|
+
*/
|
|
97
|
+
getEventAnnotations(content) {
|
|
98
|
+
const eventLevelAnnotationsMapping = {
|
|
99
|
+
'x-sap-event-spec-version': 'EventSpecVersion', // expected: string
|
|
100
|
+
'x-sap-event-source': 'EventSource', // expected: string
|
|
101
|
+
'x-sap-event-version': 'EventSchemaVersion', // expected: string
|
|
102
|
+
'x-sap-event-source-parameters': 'EventSourceParams', // expected: object
|
|
103
|
+
'x-sap-event-characteristics': 'EventCharacteristics', // expected: object
|
|
104
|
+
'x-sap-stateInfo': 'EventStateInfo' // expected: object
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const annotations = this.getAnnotations(content, eventLevelAnnotationsMapping)
|
|
108
|
+
|
|
109
|
+
return annotations
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Returns validity of the annotation value. Here the parameter
|
|
114
|
+
* content is the annotation name and value of the annotation.
|
|
115
|
+
* @returns true/false depending on the annotation value.
|
|
116
|
+
*/
|
|
117
|
+
isValidAnnotationValue(annotation, value) {
|
|
118
|
+
const objectTypeAnnotations = ['StateInfo', 'EventSourceParams', 'EventCharacteristics', 'EventStateInfo']
|
|
119
|
+
// if the value is an array or null
|
|
120
|
+
if (Array.isArray(value) || value === null) return false
|
|
121
|
+
|
|
122
|
+
if (objectTypeAnnotations.includes(annotation) && (value instanceof Object)) {
|
|
123
|
+
return true
|
|
124
|
+
} else if ((!objectTypeAnnotations.includes(annotation)) && (typeof value === 'string')) {
|
|
125
|
+
return true
|
|
126
|
+
} else {
|
|
127
|
+
console.warn('WARNING: Annotations which are not following the expected type are omitted.')
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Returns annotations for an event/service. Here the parameter content
|
|
134
|
+
* is event/service content along with the corresponding mapping.
|
|
135
|
+
* @returns an object with the annotations as key value pairs.
|
|
136
|
+
*/
|
|
137
|
+
getAnnotations(content, annotationsMapping) {
|
|
138
|
+
const annotations = {}
|
|
139
|
+
|
|
140
|
+
for (const [key, value] of Object.entries(content)) {
|
|
141
|
+
// case: known annotation
|
|
142
|
+
if (annotationsMapping[key] && this.isValidAnnotationValue(annotationsMapping[key], value)) {
|
|
143
|
+
annotations['@AsyncAPI.' + annotationsMapping[key]] = value
|
|
144
|
+
}
|
|
145
|
+
// case: unkown annotation
|
|
146
|
+
else if (key.startsWith('x-')) {
|
|
147
|
+
if (!annotations['@AsyncAPI.Extensions']) {
|
|
148
|
+
annotations['@AsyncAPI.Extensions'] = {}
|
|
149
|
+
}
|
|
150
|
+
// removes x- from the element
|
|
151
|
+
annotations['@AsyncAPI.Extensions'][key.substring(2)] = value
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return annotations
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Returns the referred schema. Here the content is the asyncapi
|
|
160
|
+
* document and payloadObject is the reference path
|
|
161
|
+
* @returns an object with the referred schema.
|
|
162
|
+
*/
|
|
163
|
+
resolveRef(content, payloadObject) {
|
|
164
|
+
let schema = {}
|
|
165
|
+
|
|
166
|
+
if (payloadObject['$ref']) {
|
|
167
|
+
const pathArray = payloadObject['$ref'].split('/')
|
|
168
|
+
pathArray.forEach((value) => {
|
|
169
|
+
if (value === '#') schema = content
|
|
170
|
+
else if (schema[value]) schema = schema[value]
|
|
171
|
+
// in case path doesn't exist in the file
|
|
172
|
+
else schema = {}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
if (!Object.keys(schema).length) {
|
|
176
|
+
console.warn('WARNING: Unable to resolve ref path: ' + payloadObject['$ref'])
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (payloadObject instanceof Object) {
|
|
180
|
+
// in case ref is not there and schema object is there
|
|
181
|
+
schema = payloadObject
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return schema
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns the properties and types generated, related to an event.
|
|
189
|
+
* @returns a properties object and a types object.
|
|
190
|
+
*/
|
|
191
|
+
getPropertiesAndTypes(eventName, content) {
|
|
192
|
+
const properties = {}
|
|
193
|
+
let types = {}
|
|
194
|
+
|
|
195
|
+
if (content.properties) {
|
|
196
|
+
for (const [key, value] of Object.entries(content.properties)) {
|
|
197
|
+
// will contain type objects generated for a property
|
|
198
|
+
let referredTypes
|
|
199
|
+
let isRequired = false
|
|
200
|
+
if (content['required']?.includes(key)) isRequired = true
|
|
201
|
+
;[properties[key], referredTypes] = this.parseProperty(eventName, key, value, isRequired)
|
|
202
|
+
// collecting all the types
|
|
203
|
+
types = { ...types, ...referredTypes }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return [properties, types]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns the processed objects for property and type.
|
|
211
|
+
* @returns property object and type object.
|
|
212
|
+
*/
|
|
213
|
+
parseProperty(eventName, propertyName, propertyContent, isRequired) {
|
|
214
|
+
let property = {}
|
|
215
|
+
let propertyAttributes
|
|
216
|
+
let types = {}
|
|
217
|
+
|
|
218
|
+
if (isRequired === true) {
|
|
219
|
+
property['@mandatory'] = true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let type = this.getTypeFromArray(propertyContent.type)
|
|
223
|
+
|
|
224
|
+
// case where type is defined
|
|
225
|
+
if (type && (type === 'object' || type === 'array')) {
|
|
226
|
+
// for generation of type: event name + property name
|
|
227
|
+
propertyName = propertyName[0].toUpperCase() + propertyName.substring(1)
|
|
228
|
+
let typeName = eventName + '.' + propertyName
|
|
229
|
+
// generate object for the introduced type
|
|
230
|
+
const elements = this.getTypeElements(propertyContent)
|
|
231
|
+
types[typeName] = { 'kind': 'type', '@cds.external': 'true', ...elements }
|
|
232
|
+
// referring to the generated type from the property
|
|
233
|
+
propertyAttributes = { 'type': typeName }
|
|
234
|
+
} else {
|
|
235
|
+
propertyAttributes = this.getPropertyObject(propertyContent)
|
|
236
|
+
}
|
|
237
|
+
property = { ...property, ...propertyAttributes }
|
|
238
|
+
|
|
239
|
+
return [property, types]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Returns generated type for the property.
|
|
244
|
+
* @returns an object for a type.
|
|
245
|
+
*/
|
|
246
|
+
getTypeElements(propertyContent) {
|
|
247
|
+
let properties = {}
|
|
248
|
+
let type = this.getTypeFromArray(propertyContent.type)
|
|
249
|
+
|
|
250
|
+
// for nested object
|
|
251
|
+
if (type === 'object') {
|
|
252
|
+
for (const [key, value] of Object.entries(propertyContent.properties)) {
|
|
253
|
+
properties[key] = this.getTypeElements(value)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// for nested array
|
|
257
|
+
else if (type === 'array') {
|
|
258
|
+
properties.items = this.getTypeElements(propertyContent.items)
|
|
259
|
+
return properties
|
|
260
|
+
}
|
|
261
|
+
// base data type mapping
|
|
262
|
+
else {
|
|
263
|
+
return this.getPropertyObject(propertyContent)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { elements: properties }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Returns type for the property from the AsyncAPI Document.
|
|
271
|
+
* @returns the value of type.
|
|
272
|
+
*/
|
|
273
|
+
getTypeFromArray(typeValue) {
|
|
274
|
+
let type
|
|
275
|
+
// if type is an array
|
|
276
|
+
if (Array.isArray(typeValue)) {
|
|
277
|
+
// one of the value will be null
|
|
278
|
+
type = typeValue[0] === 'null' ? typeValue[1] : typeValue[0]
|
|
279
|
+
} else {
|
|
280
|
+
type = typeValue
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return type
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Returns generated object for a single property.
|
|
288
|
+
* @returns an object for a property.
|
|
289
|
+
*/
|
|
290
|
+
getPropertyObject(propertyContent) {
|
|
291
|
+
const property = {}
|
|
292
|
+
if ('$ref' in propertyContent) {
|
|
293
|
+
const refPathParts = propertyContent['$ref'].split('/')
|
|
294
|
+
const propertyName = refPathParts[refPathParts.length - 1]
|
|
295
|
+
property['type'] = { 'ref': [refPathParts[3], propertyName] }
|
|
296
|
+
return property
|
|
297
|
+
}
|
|
298
|
+
const inputType = this.getTypeFromArray(propertyContent.type)
|
|
299
|
+
const cdsType = this.getDataType(inputType, propertyContent.format)
|
|
300
|
+
|
|
301
|
+
property['type'] = cdsType
|
|
302
|
+
|
|
303
|
+
// case: cds type is decimal
|
|
304
|
+
if (cdsType === 'cds.Decimal') {
|
|
305
|
+
if (propertyContent['x-sap-precision']) property.precision = propertyContent['x-sap-precision']
|
|
306
|
+
if (propertyContent['x-sap-scale']) property.scale = propertyContent['x-sap-scale']
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (propertyContent['default']) {
|
|
310
|
+
property['default'] = { 'val': propertyContent['default'] }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (propertyContent['enum']) {
|
|
314
|
+
property['enum'] = this.getEnumObject(propertyContent['enum'])
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return property
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Takes the enums from the AsyncAPI Document.
|
|
322
|
+
* @returns the property object of enums.
|
|
323
|
+
*/
|
|
324
|
+
getEnumObject(enumArray) {
|
|
325
|
+
const enumProperty = {}
|
|
326
|
+
|
|
327
|
+
enumArray.forEach(val => {
|
|
328
|
+
enumProperty[val] = {}
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
return enumProperty
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Takes the type and format from the document and returns the corresponding cds type.
|
|
336
|
+
* @returns the corresponding cds type.
|
|
337
|
+
*/
|
|
338
|
+
getDataType(type, format = null) {
|
|
339
|
+
const asyncapiToCdsMapping = {
|
|
340
|
+
'number': 'cds.Double',
|
|
341
|
+
'boolean': 'cds.Boolean',
|
|
342
|
+
'integer': 'cds.Integer'
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (type === 'string') {
|
|
346
|
+
switch (format) {
|
|
347
|
+
case 'int64':
|
|
348
|
+
return 'cds.Integer64'
|
|
349
|
+
case 'decimal':
|
|
350
|
+
return 'cds.Decimal'
|
|
351
|
+
case 'uuid':
|
|
352
|
+
return 'cds.UUID'
|
|
353
|
+
case 'date':
|
|
354
|
+
return 'cds.Date'
|
|
355
|
+
case 'partial-time':
|
|
356
|
+
return 'cds.Time'
|
|
357
|
+
case 'date-time':
|
|
358
|
+
return 'cds.Timestamp'
|
|
359
|
+
default:
|
|
360
|
+
return 'cds.LargeString'
|
|
361
|
+
}
|
|
362
|
+
} else if (asyncapiToCdsMapping[type]) {
|
|
363
|
+
return asyncapiToCdsMapping[type]
|
|
364
|
+
} else {
|
|
365
|
+
throw new Error(messages.UNRESOLVED_TYPE + `'${type}'`)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|