Skip to content

Commit

Permalink
feat!: support asyncapi v3 (#294)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukasz Gornicki <[email protected]>
  • Loading branch information
kaushik-rishi and derberg authored May 7, 2024
1 parent a6dd628 commit 87aa08b
Show file tree
Hide file tree
Showing 15 changed files with 1,816 additions and 575 deletions.
1 change: 1 addition & 0 deletions .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sonar.exclusions=test/integration.test.js
9 changes: 2 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ $ npm install -g @asyncapi/cli

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

# OR

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

##
## Install dependencies of generated library
Expand Down Expand Up @@ -100,10 +100,6 @@ $ npm run test:example
$ npm install mqtt -g

#You should see the sent message in the logs of the started example.
#Notice that the server automatically validates incoming messages and logs out validation errors

#publish an invalid message.
$ 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"}'

#publish a valid message
$ 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"}'
Expand All @@ -123,7 +119,6 @@ To avoid this, user code remains external to the generated code, functioning as
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.

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.
```

## Template configuration

Expand Down
10 changes: 3 additions & 7 deletions helpers/channels-topics.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,8 @@ export function toHermesTopic(str) {
return str.replace(/\{([^}]+)\}/g, ':$1');
}

export function channelNamesWithPublish(asyncapi) {
const result = [];
asyncapi.channelNames().forEach((name) => {
if (asyncapi.channel(name).hasPublish()) result.push(name);
});
return result;
export function channelNamesWithReceive(asyncapi) {
return asyncapi.channels().filterByReceive().map(channel => channel.address());
}

export function host(url) {
Expand Down Expand Up @@ -151,4 +147,4 @@ export function getProtocol(p) {
// https://mozilla.github.io/nunjucks/templating.html#dump
export function dump(obj) {
return JSON.stringify(obj);
}
}
462 changes: 244 additions & 218 deletions package-lock.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@
"@asyncapi/generator-hooks": "^0.1.0",
"@asyncapi/generator-react-sdk": "^1.0.18",
"eslint-plugin-react": "^7.34.1",
"filenamify": "^4.1.0",
"filenamify": "^4.3.0",
"js-beautify": "^1.15.1",
"lodash": "^4.17.15",
"lodash": "^4.17.21",
"markdown-toc": "^1.2.0"
},
"devDependencies": {
"@asyncapi/generator": "^1.17.25",
"eslint": "^8.7.0",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^25.7.0",
"eslint-plugin-sonarjs": "^0.11.0",
"jest": "^27.3.1",
"node-fetch": "^2.6.1",
"rimraf": "^5.0.1"
"jest": "^27.5.1",
"node-fetch": "^2.7.0",
"rimraf": "^5.0.5"
},
"generator": {
"apiVersion": "v3",
"supportedProtocols": [
"amqp",
"mqtt",
Expand Down Expand Up @@ -94,4 +95,4 @@
"^nimma/(.*)": "<rootDir>/node_modules/nimma/dist/cjs/$1"
}
}
}
}
17 changes: 16 additions & 1 deletion template/README.md.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { File } from '@asyncapi/generator-react-sdk';

export default function readmeFile({asyncapi, params}) {
const server = asyncapi.allServers().get(params.server);
const protocol = server.protocol();
const security = server.security();

let hasSecuritySchemeX509 = false;
let securitySchemeType;
if (params.securityScheme && security && security.length > 0) {
const securityReq = security[0].all();
if (securityReq && securityReq.length > 0) {
securitySchemeType = securityReq[0].scheme().type();
}
}

hasSecuritySchemeX509 = (params.securityScheme && (protocol === 'kafka' || protocol === 'kafka-secure') && securitySchemeType === 'X509');

return <File name={'README.md'}>
{`# ${ asyncapi.info().title() }
Expand All @@ -12,7 +27,7 @@ ${ asyncapi.info().description() || '' }
\`\`\`sh
npm i
\`\`\`
${(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.' : ''}
${ 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.' : ''}
## Import and start
Expand Down
27 changes: 17 additions & 10 deletions template/config/common.yml.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { File } from '@asyncapi/generator-react-sdk';
import { camelCase, channelNamesWithPublish, dump, host, port, queueName, stripProtocol, toAmqpTopic, toKafkaTopic, toMqttTopic } from '../../helpers/index';
import { camelCase, channelNamesWithReceive, dump, host, port, queueName, stripProtocol, toAmqpTopic, toKafkaTopic, toMqttTopic } from '../../helpers/index';
import { replaceServerVariablesWithValues } from '@asyncapi/generator-filters/src/customFilters';

export default function CommonConfigYAMLRender({ asyncapi, params }) {
const serverProtocol = asyncapi.server(params.server).protocol();
const serverVariables = asyncapi.server(params.server).variables();
const resolvedBrokerUrlWithReplacedVariables = replaceServerVariablesWithValues(asyncapi.server(params.server).url(), serverVariables);
const server = asyncapi.allServers().get(params.server);
const serverProtocol = server.protocol();
const serverVariablesArray = server.variables();
const serverVariables = {};
serverVariablesArray.forEach(item => {
serverVariables[item.id()] = item;
});

const resolvedBrokerUrlWithReplacedVariables = replaceServerVariablesWithValues(server.url(), serverVariables);

return (
<File name={'common.yml'}>
Expand Down Expand Up @@ -47,7 +53,7 @@ function amqpBlock(url, asyncapi) {
password:
host: ${host(url)}
port:
topics: ${dump(toAmqpTopic(channelNamesWithPublish(asyncapi)))}
topics: ${dump(toAmqpTopic(channelNamesWithReceive(asyncapi)))}
queue: ${queueName(asyncapi.info().title(), asyncapi.info().version())}
queueOptions:
exclusive: false
Expand All @@ -57,9 +63,10 @@ function amqpBlock(url, asyncapi) {
}

function mqttBlock(url, asyncapi, params) {
const server = asyncapi.allServers().get(params.server);
return ` mqtt:
url: ${asyncapi.server(params.server).protocol()}://${stripProtocol(url)}
topics: ${dump(toMqttTopic(channelNamesWithPublish(asyncapi)))}
url: ${server.protocol()}://${stripProtocol(url)}
topics: ${dump(toMqttTopic(channelNamesWithReceive(asyncapi)))}
qos:
protocol: mqtt
retain:
Expand All @@ -75,7 +82,7 @@ function kafkaBlock(url, asyncapi) {
consumerOptions:
groupId: ${camelCase(asyncapi.info().title())}
topics:
${channelNamesWithPublish(asyncapi).map(topic => `- ${toKafkaTopic(topic)}`).join('\n')}
${channelNamesWithReceive(asyncapi).map(topic => `- ${toKafkaTopic(topic)}`).join('\n')}
topicSeparator: '__'
topicPrefix:
`;
Expand All @@ -87,12 +94,12 @@ function kafkaProductionBlock(params, asyncapi) {
ssl:
rejectUnauthorized: true
`;
if (params.securityScheme && asyncapi.components().securityScheme(params.securityScheme).type() !== 'X509') {
if (params.securityScheme && asyncapi.components().securitySchemes().get(params.securityScheme).type() !== 'X509') {
productionBlock += ` sasl:
mechanism: 'plain'
username:
password:
`;
}
return productionBlock;
}
}
9 changes: 7 additions & 2 deletions template/package.json.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ export default function packageFile({ asyncapi, params }) {
dotenv: '8.1.0',
hermesjs: '2.x',
'hermesjs-router': '1.x',
'asyncapi-validator': '3.0.0',
'node-fetch': '2.6.0',
'node-yaml-config': '0.0.4',
};

const serverProtocol = asyncapi.server(params.server).protocol();
const majorSpecVersion = parseInt(asyncapi.version().split('.')[0], 10);
const isSpecV3 = (majorSpecVersion === 3);
if (!isSpecV3) {
dependencies['asyncapi-validator'] = '3.0.0';
}

const serverProtocol = asyncapi.allServers().get(params.server).protocol();
if (serverProtocol === 'mqtt' || serverProtocol === 'mqtts') {
dependencies['hermesjs-mqtt'] = '2.x';
} else if (serverProtocol === 'kafka' || serverProtocol === 'kafka-secure') {
Expand Down
Loading

0 comments on commit 87aa08b

Please sign in to comment.