Skip to content

Commit 87aa08b

Browse files
feat!: support asyncapi v3 (#294)
Co-authored-by: Lukasz Gornicki <lpgornicki@gmail.com>
1 parent a6dd628 commit 87aa08b

15 files changed

Lines changed: 1816 additions & 575 deletions

File tree

.sonarcloud.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sonar.exclusions=test/integration.test.js

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ $ npm install -g @asyncapi/cli
6464

6565
# Run generation from the root of the template to use tes MQTT example
6666
# To use the template
67-
$ asyncapi generate fromTemplate test/mocks/mqtt/asyncapi.yml @asyncapi/nodejs-template -o test/output -p server=production
67+
$ asyncapi generate fromTemplate test/mocks/mqtt/asyncapi-v3.yml @asyncapi/nodejs-template -o test/output -p server=production
6868

6969
# OR
7070

7171
# To test your local changes provided inside template
72-
$ asyncapi generate fromTemplate test/mocks/mqtt/asyncapi.yml ./ -o test/output -p server=production
72+
$ asyncapi generate fromTemplate test/mocks/mqtt/asyncapi-v3.yml ./ -o test/output -p server=production
7373

7474
##
7575
## Install dependencies of generated library
@@ -100,10 +100,6 @@ $ npm run test:example
100100
$ npm install mqtt -g
101101

102102
#You should see the sent message in the logs of the started example.
103-
#Notice that the server automatically validates incoming messages and logs out validation errors
104-
105-
#publish an invalid message.
106-
$ mqtt pub -t 'smartylighting/streetlights/1/0/event/123/lighting/measured' -h 'test.mosquitto.org' -m '{"id": 1, "lumens": "3", "sentAt": "2017-06-07T12:34:32.000Z"}'
107103

108104
#publish a valid message
109105
$ mqtt pub -t 'smartylighting/streetlights/1/0/event/123/lighting/measured' -h 'test.mosquitto.org' -m '{"id": 1, "lumens": 3, "sentAt": "2017-06-07T12:34:32.000Z"}'
@@ -123,7 +119,6 @@ To avoid this, user code remains external to the generated code, functioning as
123119
Facilitating this separation involves creating handlers and associating them with their respective routes. These handlers can then be seamlessly integrated into the template's workflow by importing the appropriate methods to register the handlers. In doing so, the template's `client.register<operationId>Middleware` method becomes the bridge between the user-written handlers and the generated code. This can be used to register middlewares for specific methods on specific channels.
124120

125121
Look closely into [example script](test/example/script.js) that works with library generated using [this MQTT based AsyncAPI document](test/mocks/mqtt/asyncapi.yml). Look at available handlers API for reading incomming messages and processing outgoing messages. Learn how to start generated server with `init()` and also learn how to send messages, if needed.
126-
```
127122

128123
## Template configuration
129124

helpers/channels-topics.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,8 @@ export function toHermesTopic(str) {
5050
return str.replace(/\{([^}]+)\}/g, ':$1');
5151
}
5252

53-
export function channelNamesWithPublish(asyncapi) {
54-
const result = [];
55-
asyncapi.channelNames().forEach((name) => {
56-
if (asyncapi.channel(name).hasPublish()) result.push(name);
57-
});
58-
return result;
53+
export function channelNamesWithReceive(asyncapi) {
54+
return asyncapi.channels().filterByReceive().map(channel => channel.address());
5955
}
6056

6157
export function host(url) {
@@ -151,4 +147,4 @@ export function getProtocol(p) {
151147
// https://mozilla.github.io/nunjucks/templating.html#dump
152148
export function dump(obj) {
153149
return JSON.stringify(obj);
154-
}
150+
}

package-lock.json

Lines changed: 244 additions & 218 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,22 @@
3636
"@asyncapi/generator-hooks": "^0.1.0",
3737
"@asyncapi/generator-react-sdk": "^1.0.18",
3838
"eslint-plugin-react": "^7.34.1",
39-
"filenamify": "^4.1.0",
39+
"filenamify": "^4.3.0",
4040
"js-beautify": "^1.15.1",
41-
"lodash": "^4.17.15",
41+
"lodash": "^4.17.21",
4242
"markdown-toc": "^1.2.0"
4343
},
4444
"devDependencies": {
4545
"@asyncapi/generator": "^1.17.25",
46-
"eslint": "^8.7.0",
46+
"eslint": "^8.57.0",
4747
"eslint-plugin-jest": "^25.7.0",
4848
"eslint-plugin-sonarjs": "^0.11.0",
49-
"jest": "^27.3.1",
50-
"node-fetch": "^2.6.1",
51-
"rimraf": "^5.0.1"
49+
"jest": "^27.5.1",
50+
"node-fetch": "^2.7.0",
51+
"rimraf": "^5.0.5"
5252
},
5353
"generator": {
54+
"apiVersion": "v3",
5455
"supportedProtocols": [
5556
"amqp",
5657
"mqtt",
@@ -94,4 +95,4 @@
9495
"^nimma/(.*)": "<rootDir>/node_modules/nimma/dist/cjs/$1"
9596
}
9697
}
97-
}
98+
}

template/README.md.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import { File } from '@asyncapi/generator-react-sdk';
22

33
export default function readmeFile({asyncapi, params}) {
4+
const server = asyncapi.allServers().get(params.server);
5+
const protocol = server.protocol();
6+
const security = server.security();
7+
8+
let hasSecuritySchemeX509 = false;
9+
let securitySchemeType;
10+
if (params.securityScheme && security && security.length > 0) {
11+
const securityReq = security[0].all();
12+
if (securityReq && securityReq.length > 0) {
13+
securitySchemeType = securityReq[0].scheme().type();
14+
}
15+
}
16+
17+
hasSecuritySchemeX509 = (params.securityScheme && (protocol === 'kafka' || protocol === 'kafka-secure') && securitySchemeType === 'X509');
18+
419
return <File name={'README.md'}>
520
{`# ${ asyncapi.info().title() }
621
@@ -12,7 +27,7 @@ ${ asyncapi.info().description() || '' }
1227
\`\`\`sh
1328
npm i
1429
\`\`\`
15-
${(params.securityScheme && (asyncapi.server(params.server).protocol() === 'kafka' || asyncapi.server(params.server).protocol() === 'kafka-secure') && asyncapi.components().securityScheme(params.securityScheme).type() === 'X509') ? '1. (Optional) For X509 security provide files with all data required to establish secure connection using certificates. Place files like `ca.pem`, `service.cert`, `service.key` in the root of the project or the location that you explicitly specified during generation.' : ''}
30+
${ hasSecuritySchemeX509 ? '1. (Optional) For X509 security provide files with all data required to establish secure connection using certificates. Place files like `ca.pem`, `service.cert`, `service.key` in the root of the project or the location that you explicitly specified during generation.' : ''}
1631
1732
## Import and start
1833

template/config/common.yml.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { File } from '@asyncapi/generator-react-sdk';
2-
import { camelCase, channelNamesWithPublish, dump, host, port, queueName, stripProtocol, toAmqpTopic, toKafkaTopic, toMqttTopic } from '../../helpers/index';
2+
import { camelCase, channelNamesWithReceive, dump, host, port, queueName, stripProtocol, toAmqpTopic, toKafkaTopic, toMqttTopic } from '../../helpers/index';
33
import { replaceServerVariablesWithValues } from '@asyncapi/generator-filters/src/customFilters';
44

55
export default function CommonConfigYAMLRender({ asyncapi, params }) {
6-
const serverProtocol = asyncapi.server(params.server).protocol();
7-
const serverVariables = asyncapi.server(params.server).variables();
8-
const resolvedBrokerUrlWithReplacedVariables = replaceServerVariablesWithValues(asyncapi.server(params.server).url(), serverVariables);
6+
const server = asyncapi.allServers().get(params.server);
7+
const serverProtocol = server.protocol();
8+
const serverVariablesArray = server.variables();
9+
const serverVariables = {};
10+
serverVariablesArray.forEach(item => {
11+
serverVariables[item.id()] = item;
12+
});
13+
14+
const resolvedBrokerUrlWithReplacedVariables = replaceServerVariablesWithValues(server.url(), serverVariables);
915

1016
return (
1117
<File name={'common.yml'}>
@@ -47,7 +53,7 @@ function amqpBlock(url, asyncapi) {
4753
password:
4854
host: ${host(url)}
4955
port:
50-
topics: ${dump(toAmqpTopic(channelNamesWithPublish(asyncapi)))}
56+
topics: ${dump(toAmqpTopic(channelNamesWithReceive(asyncapi)))}
5157
queue: ${queueName(asyncapi.info().title(), asyncapi.info().version())}
5258
queueOptions:
5359
exclusive: false
@@ -57,9 +63,10 @@ function amqpBlock(url, asyncapi) {
5763
}
5864

5965
function mqttBlock(url, asyncapi, params) {
66+
const server = asyncapi.allServers().get(params.server);
6067
return ` mqtt:
61-
url: ${asyncapi.server(params.server).protocol()}://${stripProtocol(url)}
62-
topics: ${dump(toMqttTopic(channelNamesWithPublish(asyncapi)))}
68+
url: ${server.protocol()}://${stripProtocol(url)}
69+
topics: ${dump(toMqttTopic(channelNamesWithReceive(asyncapi)))}
6370
qos:
6471
protocol: mqtt
6572
retain:
@@ -75,7 +82,7 @@ function kafkaBlock(url, asyncapi) {
7582
consumerOptions:
7683
groupId: ${camelCase(asyncapi.info().title())}
7784
topics:
78-
${channelNamesWithPublish(asyncapi).map(topic => `- ${toKafkaTopic(topic)}`).join('\n')}
85+
${channelNamesWithReceive(asyncapi).map(topic => `- ${toKafkaTopic(topic)}`).join('\n')}
7986
topicSeparator: '__'
8087
topicPrefix:
8188
`;
@@ -87,12 +94,12 @@ function kafkaProductionBlock(params, asyncapi) {
8794
ssl:
8895
rejectUnauthorized: true
8996
`;
90-
if (params.securityScheme && asyncapi.components().securityScheme(params.securityScheme).type() !== 'X509') {
97+
if (params.securityScheme && asyncapi.components().securitySchemes().get(params.securityScheme).type() !== 'X509') {
9198
productionBlock += ` sasl:
9299
mechanism: 'plain'
93100
username:
94101
password:
95102
`;
96103
}
97104
return productionBlock;
98-
}
105+
}

template/package.json.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ export default function packageFile({ asyncapi, params }) {
77
dotenv: '8.1.0',
88
hermesjs: '2.x',
99
'hermesjs-router': '1.x',
10-
'asyncapi-validator': '3.0.0',
1110
'node-fetch': '2.6.0',
1211
'node-yaml-config': '0.0.4',
1312
};
1413

15-
const serverProtocol = asyncapi.server(params.server).protocol();
14+
const majorSpecVersion = parseInt(asyncapi.version().split('.')[0], 10);
15+
const isSpecV3 = (majorSpecVersion === 3);
16+
if (!isSpecV3) {
17+
dependencies['asyncapi-validator'] = '3.0.0';
18+
}
19+
20+
const serverProtocol = asyncapi.allServers().get(params.server).protocol();
1621
if (serverProtocol === 'mqtt' || serverProtocol === 'mqtts') {
1722
dependencies['hermesjs-mqtt'] = '2.x';
1823
} else if (serverProtocol === 'kafka' || serverProtocol === 'kafka-secure') {

0 commit comments

Comments
 (0)