diff --git a/README.md b/README.md index 8f9c13b7..07dc4d66 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - [Supported protocols](#supported-protocols) - [How to use the template](#how-to-use-the-template) * [CLI](#cli) + * [Adding custom code](#adding-custom-code--handlers) - [Template configuration](#template-configuration) - [Development](#development) - [Contributors](#contributors) @@ -100,6 +101,108 @@ $ mqtt pub -t 'smartylighting/streetlights/1/0/event/123/lighting/measured' -h ' #Notice that the server automatically validates incoming messages and logs out validation errors ``` +### Adding custom code / handlers + +It's highly recommended to treat the generated template as a library or API for initializing the server and integrating user-written handlers. Instead of directly modifying the template, leveraging it in this manner ensures that its regenerative capability is preserved. Any modifications made directly to the template would be overwritten upon regeneration. + +Consider a scenario where you intend to introduce a new channel or section to the AsyncAPI file, followed by a template regeneration. In this case, any modifications applied within the generated code would be overwritten. + +To avoid this, user code remains external to the generated code, functioning as an independent entity that consumes the generated code as a library. By adopting this approach, the user code remains unaffected during template regenerations. + +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.registerMiddleware` 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. + +> The AsyncAPI file used for the example is [here](https://bit.ly/asyncapi) + +```js +// output refers to the generated template folder +// You require the generated server. Running this code starts the server +// App exposes API to send messages +const { client } = require("./output"); + +// to start the app +client.init(); + +// Generated handlers that we use to react on consumer / produced messages are attached to the client +// through which we can register middleware functions + +/** + * + * + * Example of how to process a message before it is sent to the broker + * + * + */ +function testPublish() { + // mosquitto_sub -h test.mosquitto.org -p 1883 -t "smartylighting/streetlights/1/0/action/12/turn/on" + + // Registering your custom logic in a channel-specific handler + // the passed handler function is called once the app sends a message to the channel + // For example `client.app.send` sends a message to some channel using and before it is sent, you want to perform some other actions + // in such a case, you can register middlewares like below + client.registerTurnOnMiddleware((message) => { // `turnOn` is the respective operationId + console.log("hitting the middleware before publishing the message"); + console.log( + `sending turn on message to streetlight ${message.params.streetlightId}`, + message.payload + ); + }); + + client.app.send( + { command: "off" }, + {}, + "smartylighting/streetlights/1/0/action/12/turn/on" + ); +} + + +/** + * + * + * Example of how to work with generated code as a consumer + * + * +*/ +function testSubscribe() { + // mosquitto_pub -h test.mosquitto.org -p 1883 -t "smartylighting/streetlights/1/0/event/101/lighting/measured" -m '{"lumens": 10}' + + // Writing your custom logic that should be triggered when your app receives as message from a given channel + // Registering your custom logic in a channel-specific handler + // the passed handler functions are called once the app gets message sent to the channel + + client.registerReceiveLightMeasurementMiddleware((message) => { // `recieveLightMeasurement` is the respective operationId + console.log("recieved in middleware 1", message.payload); + }); + + client.registerReceiveLightMeasurementMiddleware((message) => { + console.log("recieved in middleware 2", message.payload); + }); +} + +testPublish(); +testSubscribe(); + +/** + * + * + * Example of how to produce a message using API of generated app independently from the handlers + * + * +*/ + +(function myLoop (i) { + setTimeout(() => { + console.log('producing custom message'); + client.app.send({percentage: 1}, {}, 'smartylighting/streetlights/1/0/action/1/turn/on'); + if (--i) myLoop(i); + }, 1000); +}(3)); +``` + +You can run the above code and test the working of the handlers by sending a message using the mqtt cli / mosquitto broker software to the `smartylighting/streetlights/1/0/event/123/lighting/measured` channel using this command +`mosquitto_pub -h test.mosquitto.org -p 1883 -t "smartylighting/streetlights/1/0/event/101/lighting/measured" -m '{"lumens": 10, "sentAt": "2017-06-07T12:34:32.000Z"}'` +or +`mqtt pub -t 'smartylighting/streetlights/1/0/event/123/lighting/measured' -h 'test.mosquitto.org' -m '{"id": 1, "lumens": 3, }'` (if you are using the mqtt cli) + ## Template configuration You can configure this template by passing different parameters in the Generator CLI: `-p PARAM1_NAME=PARAM1_VALUE -p PARAM2_NAME=PARAM2_VALUE` diff --git a/filters/all.js b/filters/all.js index 91b0603d..5d194dc3 100644 --- a/filters/all.js +++ b/filters/all.js @@ -132,6 +132,12 @@ function trimLastChar(string) { } filter.trimLastChar = trimLastChar; +function convertOpertionIdToMiddlewareFn(operationId) { + const capitalizedOperationId = operationId.charAt(0).toUpperCase() + operationId.slice(1); + return `register${ capitalizedOperationId }Middleware`; +} +filter.convertOpertionIdToMiddlewareFn = convertOpertionIdToMiddlewareFn; + function toJS(objFromJSON, indent = 2) { if (typeof objFromJSON !== 'object' || Array.isArray(objFromJSON)) { // not an object, stringify using native function diff --git a/template/package.json b/template/package.json index 9657a9e9..362006a6 100644 --- a/template/package.json +++ b/template/package.json @@ -2,6 +2,7 @@ "name": "{{ asyncapi.info().title() | kebabCase }}", "description": "{{ asyncapi.info().description() | oneLine }}", "version": "{{ asyncapi.info().version() }}", + "main": "./src/api", "scripts": { "start": "node src/api/index.js" }, diff --git a/template/src/api/handlers/$$channel$$.js b/template/src/api/handlers/$$channel$$.js index 477675fd..037fc5e8 100644 --- a/template/src/api/handlers/$$channel$$.js +++ b/template/src/api/handlers/$$channel$$.js @@ -1,8 +1,27 @@ {%- if channel.hasPublish() and channel.publish().ext('x-lambda') %}const fetch = require('node-fetch');{%- endif %} const handler = module.exports = {}; + {% if channel.hasPublish() %} +const {{ channel.publish().id() }}Middlewares = []; + +/** + * Registers a middleware function for the {{ channel.publish().id() }} operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.{{ channel.publish().id() | convertOpertionIdToMiddlewareFn }} = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + {{ channel.publish().id() }}Middlewares.push(middlewareFn); +} + /** * {{ channel.publish().summary() }} + * * @param {object} options * @param {object} options.message {%- if channel.publish().message(0).headers() %} @@ -16,7 +35,7 @@ const handler = module.exports = {}; {%- endfor %} {%- endif %} */ -handler.{{ channel.publish().id() }} = async ({message}) => { +handler._{{ channel.publish().id() }} = async ({message}) => { {%- if channel.publish().ext('x-lambda') %} {%- set lambda = channel.publish().ext('x-lambda') %} fetch('{{ lambda.url }}', { @@ -30,29 +49,52 @@ handler.{{ channel.publish().id() }} = async ({message}) => { .then(json => console.log(json)) .catch(err => { throw err; }); {%- else %} - // Implement your business logic here... + for (const middleware of {{ channel.publish().id() }}Middlewares) { + await middleware(message); + } {%- endif %} }; {%- endif %} + {%- if channel.hasSubscribe() %} +const {{ channel.subscribe().id() }}Middlewares = []; + +/** + * Registers a middleware function for the {{ channel.subscribe().id() }} operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.{{ channel.subscribe().id() | convertOpertionIdToMiddlewareFn }} = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + {{ channel.subscribe().id() }}Middlewares.push(middlewareFn); +} + /** * {{ channel.subscribe().summary() }} + * * @param {object} options * @param {object} options.message -{%- if channel.subscribe().message(0).headers() %} -{%- for fieldName, field in channel.subscribe().message(0).headers().properties() %} -{{ field | docline(fieldName, 'options.message.headers') }} -{%- endfor %} -{%- endif %} -{%- if channel.subscribe().message(0).payload() %} -{%- for fieldName, field in channel.subscribe().message(0).payload().properties() %} -{{ field | docline(fieldName, 'options.message.payload') }} -{%- endfor %} -{%- endif %} + {%- if channel.subscribe().message(0).headers() %} + {%- for fieldName, field in channel.subscribe().message(0).headers().properties() %} + {{ field | docline(fieldName, 'options.message.headers') }} + {%- endfor %} + {%- endif %} + {%- if channel.subscribe().message(0).payload() %} + {%- for fieldName, field in channel.subscribe().message(0).payload().properties() %} + {{ field | docline(fieldName, 'options.message.payload') }} + {%- endfor %} + {%- endif %} */ -handler.{{ channel.subscribe().id() }} = async ({message}) => { - // Implement your business logic here... +handler._{{ channel.subscribe().id() }} = async ({message}) => { + for (const middleware of {{ channel.subscribe().id() }}Middlewares) { + await middleware(message); + } }; {%- endif %} diff --git a/template/src/api/index.js b/template/src/api/index.js index d10df066..5ceef743 100644 --- a/template/src/api/index.js +++ b/template/src/api/index.js @@ -54,12 +54,35 @@ app.useOutbound(errorLogger); app.useOutbound(logger); app.useOutbound(json2string); -app - .listen() - .then((adapters) => { - console.log(cyan.underline(`${config.app.name} ${config.app.version}`), gray('is ready!'), '\n'); - adapters.forEach(adapter => { - console.log('🔗 ', adapter.name(), gray('is connected!')); - }); - }) - .catch(console.error); +function init() { + app + .listen() + .then((adapters) => { + console.log(cyan.underline(`${config.app.name} ${config.app.version}`), gray('is ready!'), '\n'); + adapters.forEach(adapter => { + console.log('🔗 ', adapter.name(), gray('is connected!')); + }); + }) + .catch(console.error); +} + +const handlers = { +{%- for channelName, channel in asyncapi.channels() -%} +{% if channel.hasPublish() %} + {{ channel.publish().id() | convertOpertionIdToMiddlewareFn }}: require('./handlers/{{ channelName | convertToFilename }}').{{ channel.publish().id() | convertOpertionIdToMiddlewareFn }}, +{%- endif -%} +{% if channel.hasSubscribe() %} + {{ channel.subscribe().id() | convertOpertionIdToMiddlewareFn }}: require('./handlers/{{ channelName | convertToFilename }}').{{ channel.subscribe().id() | convertOpertionIdToMiddlewareFn }}, +{% endif %} +{%- endfor -%} +}; + +const client = { + app, + init, + ...handlers +}; + +module.exports = { + client +}; diff --git a/template/src/api/routes/$$channel$$.js b/template/src/api/routes/$$channel$$.js index ff1ed0c7..9f5b0779 100644 --- a/template/src/api/routes/$$channel$$.js +++ b/template/src/api/routes/$$channel$$.js @@ -29,14 +29,14 @@ router.use('{{ channelName | toHermesTopic }}', async (message, next) => { } catch { }; {% endfor -%} if (nValidated === 1) { - await {{ channelName | camelCase }}Handler.{{ channel.publish().id() }}({message}); + await {{ channelName | camelCase }}Handler._{{ channel.publish().id() }}({message}); next() } else { throw new Error(`${nValidated} of {{ channel.publish().messages().length }} message schemas matched when exactly 1 should match`); } {% else %} await validateMessage(message.payload,'{{ channelName }}','{{ channel.publish().message().name() }}','publish'); - await {{ channelName | camelCase }}Handler.{{ channel.publish().id() }}({message}); + await {{ channelName | camelCase }}Handler._{{ channel.publish().id() }}({message}); next(); {% endif %} } catch (e) { @@ -61,14 +61,14 @@ router.useOutbound('{{ channelName | toHermesTopic }}', async (message, next) => nValidated = await validateMessage(message.payload,'{{ channelName }}','{{ channel.subscribe().message(i).name() }}','subscribe', nValidated); {% endfor -%} if (nValidated === 1) { - await {{ channelName | camelCase }}Handler.{{ channel.subscribe().id() }}({message}); + await {{ channelName | camelCase }}Handler._{{ channel.subscribe().id() }}({message}); next() } else { throw new Error(`${nValidated} of {{ channel.subscribe().messages().length }} message schemas matched when exactly 1 should match`); } {% else %} await validateMessage(message.payload,'{{ channelName }}','{{ channel.subscribe().message().name() }}','subscribe'); - await {{ channelName | camelCase }}Handler.{{ channel.subscribe().id() }}({message}); + await {{ channelName | camelCase }}Handler._{{ channel.subscribe().id() }}({message}); next(); {% endif %} } catch (e) { diff --git a/template/src/lib/message-validator.js b/template/src/lib/message-validator.js index f156e083..0a0e4e29 100644 --- a/template/src/lib/message-validator.js +++ b/template/src/lib/message-validator.js @@ -1,8 +1,10 @@ +const path = require('path'); const AsyncApiValidator = require('asyncapi-validator'); // Try to parse the payload, and increment nValidated if parsing was successful. module.exports.validateMessage = async (payload, channelName, messageName, operation, nValidated=0) => { - const va = await AsyncApiValidator.fromSource('./asyncapi.yaml', {msgIdentifier: 'name'}); + const asyncApiFilePath = path.resolve(__dirname, '../../asyncapi.yaml'); + const va = await AsyncApiValidator.fromSource(asyncApiFilePath, {msgIdentifier: 'name'}); va.validate(messageName, payload, channelName, operation); nValidated++; diff --git a/test/__snapshots__/integration.test.js.snap b/test/__snapshots__/integration.test.js.snap index 7612bf29..b38455b0 100644 --- a/test/__snapshots__/integration.test.js.snap +++ b/test/__snapshots__/integration.test.js.snap @@ -4,16 +4,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const dimLightMiddlewares = []; + +/** + * Registers a middleware function for the dimLight operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerDimLightMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + dimLightMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {integer} options.message.payload.percentage - Percentage to which the light should be dimmed to. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {integer} options.message.payload.percentage - Percentage to which the light should be dimmed to. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.dimLight = async ({message}) => { - // Implement your business logic here... +handler._dimLight = async ({message}) => { + for (const middleware of dimLightMiddlewares) { + await middleware(message); + } }; " `; @@ -22,16 +43,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const turnOffMiddlewares = []; + +/** + * Registers a middleware function for the turnOff operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerTurnOffMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + turnOffMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {string} options.message.payload.command - Whether to turn on or off the light. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {string} options.message.payload.command - Whether to turn on or off the light. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.turnOff = async ({message}) => { - // Implement your business logic here... +handler._turnOff = async ({message}) => { + for (const middleware of turnOffMiddlewares) { + await middleware(message); + } }; " `; @@ -40,16 +82,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const turnOnMiddlewares = []; + +/** + * Registers a middleware function for the turnOn operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerTurnOnMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + turnOnMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {string} options.message.payload.command - Whether to turn on or off the light. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {string} options.message.payload.command - Whether to turn on or off the light. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.turnOn = async ({message}) => { - // Implement your business logic here... +handler._turnOn = async ({message}) => { + for (const middleware of turnOnMiddlewares) { + await middleware(message); + } }; " `; @@ -58,16 +121,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const receiveLightMeasurementMiddlewares = []; + +/** + * Registers a middleware function for the receiveLightMeasurement operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerReceiveLightMeasurementMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + receiveLightMeasurementMiddlewares.push(middlewareFn); +} + /** * Inform about environmental lighting conditions of a particular streetlight. + * * @param {object} options * @param {object} options.message * @param {integer} options.message.headers.my-app-header * @param {integer} options.message.payload.lumens - Light intensity measured in lumens. * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.receiveLightMeasurement = async ({message}) => { - // Implement your business logic here... +handler._receiveLightMeasurement = async ({message}) => { + for (const middleware of receiveLightMeasurementMiddlewares) { + await middleware(message); + } }; " `; @@ -85,7 +169,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/dim', try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/dim','dimLight','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdDimHandler.dimLight({message}); + await smartylightingStreetlights10ActionStreetlightIdDimHandler._dimLight({message}); next(); } catch (e) { @@ -108,7 +192,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/turn/o try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off','turnOnOff','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdTurnOffHandler.turnOff({message}); + await smartylightingStreetlights10ActionStreetlightIdTurnOffHandler._turnOff({message}); next(); } catch (e) { @@ -131,7 +215,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/turn/o try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on','turnOnOff','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdTurnOnHandler.turnOn({message}); + await smartylightingStreetlights10ActionStreetlightIdTurnOnHandler._turnOn({message}); next(); } catch (e) { @@ -157,7 +241,7 @@ router.use('smartylighting/streetlights/1/0/event/:streetlightId/lighting/measur try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured','lightMeasured','publish'); - await smartylightingStreetlights10EventStreetlightIdLightingMeasuredHandler.receiveLightMeasurement({message}); + await smartylightingStreetlights10EventStreetlightIdLightingMeasuredHandler._receiveLightMeasurement({message}); next(); } catch (e) { @@ -206,15 +290,36 @@ app.useOutbound(errorLogger); app.useOutbound(logger); app.useOutbound(json2string); -app - .listen() - .then((adapters) => { - console.log(cyan.underline(\`\${config.app.name} \${config.app.version}\`), gray('is ready!'), '\\\\n'); - adapters.forEach(adapter => { - console.log('🔗 ', adapter.name(), gray('is connected!')); - }); - }) - .catch(console.error); +function init() { + app + .listen() + .then((adapters) => { + console.log(cyan.underline(\`\${config.app.name} \${config.app.version}\`), gray('is ready!'), '\\\\n'); + adapters.forEach(adapter => { + console.log('🔗 ', adapter.name(), gray('is connected!')); + }); + }) + .catch(console.error); +} + +const handlers = { + registerReceiveLightMeasurementMiddleware: require('./handlers/smartylighting-streetlights-1-0-event-{streetlightId}-lighting-measured').registerReceiveLightMeasurementMiddleware, + registerTurnOnMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-turn-on').registerTurnOnMiddleware, + + registerTurnOffMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-turn-off').registerTurnOffMiddleware, + + registerDimLightMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-dim').registerDimLightMiddleware, +}; + +const client = { + app, + init, + ...handlers +}; + +module.exports = { + client +}; " `; @@ -248,6 +353,7 @@ exports[`template integration tests for generated files using the generator and \\"name\\": \\"streetlights-mqtt-api\\", \\"description\\": \\"The Smartylighting Streetlights API allows you to remotely manage the city lights. ### Check out its awesome features: * Turn a specific streetlight on/off 🌃 * Dim a specific streetlight 😎 * Receive real-time information about environmental lighting conditions 📈 \\", \\"version\\": \\"1.0.0\\", + \\"main\\": \\"./src/api\\", \\"scripts\\": { \\"start\\": \\"node src/api/index.js\\" }, @@ -269,16 +375,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const dimLightMiddlewares = []; + +/** + * Registers a middleware function for the dimLight operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerDimLightMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + dimLightMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {integer} options.message.payload.percentage - Percentage to which the light should be dimmed to. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {integer} options.message.payload.percentage - Percentage to which the light should be dimmed to. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.dimLight = async ({message}) => { - // Implement your business logic here... +handler._dimLight = async ({message}) => { + for (const middleware of dimLightMiddlewares) { + await middleware(message); + } }; " `; @@ -287,16 +414,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const turnOffMiddlewares = []; + +/** + * Registers a middleware function for the turnOff operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerTurnOffMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + turnOffMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {string} options.message.payload.command - Whether to turn on or off the light. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {string} options.message.payload.command - Whether to turn on or off the light. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.turnOff = async ({message}) => { - // Implement your business logic here... +handler._turnOff = async ({message}) => { + for (const middleware of turnOffMiddlewares) { + await middleware(message); + } }; " `; @@ -305,16 +453,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const turnOnMiddlewares = []; + +/** + * Registers a middleware function for the turnOn operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerTurnOnMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + turnOnMiddlewares.push(middlewareFn); +} + /** * + * * @param {object} options * @param {object} options.message - * @param {integer} options.message.headers.my-app-header - * @param {string} options.message.payload.command - Whether to turn on or off the light. - * @param {string} options.message.payload.sentAt - Date and time when the message was sent. + * @param {integer} options.message.headers.my-app-header + * @param {string} options.message.payload.command - Whether to turn on or off the light. + * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.turnOn = async ({message}) => { - // Implement your business logic here... +handler._turnOn = async ({message}) => { + for (const middleware of turnOnMiddlewares) { + await middleware(message); + } }; " `; @@ -323,16 +492,37 @@ exports[`template integration tests for generated files using the generator and " const handler = module.exports = {}; + +const receiveLightMeasurementMiddlewares = []; + +/** + * Registers a middleware function for the receiveLightMeasurement operation to be executed during request processing. + * + * Middleware functions have access to options object that you can use to access the message content and other helper functions + * + * @param {function} middlewareFn - The middleware function to be registered. + * @throws {TypeError} If middlewareFn is not a function. + */ +handler.registerReceiveLightMeasurementMiddleware = (middlewareFn) => { + if (typeof middlewareFn !== 'function') { + throw new TypeError('middlewareFn must be a function'); + } + receiveLightMeasurementMiddlewares.push(middlewareFn); +} + /** * Inform about environmental lighting conditions of a particular streetlight. + * * @param {object} options * @param {object} options.message * @param {integer} options.message.headers.my-app-header * @param {integer} options.message.payload.lumens - Light intensity measured in lumens. * @param {string} options.message.payload.sentAt - Date and time when the message was sent. */ -handler.receiveLightMeasurement = async ({message}) => { - // Implement your business logic here... +handler._receiveLightMeasurement = async ({message}) => { + for (const middleware of receiveLightMeasurementMiddlewares) { + await middleware(message); + } }; " `; @@ -350,7 +540,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/dim', try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/dim','dimLight','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdDimHandler.dimLight({message}); + await smartylightingStreetlights10ActionStreetlightIdDimHandler._dimLight({message}); next(); } catch (e) { @@ -373,7 +563,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/turn/o try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off','turnOnOff','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdTurnOffHandler.turnOff({message}); + await smartylightingStreetlights10ActionStreetlightIdTurnOffHandler._turnOff({message}); next(); } catch (e) { @@ -396,7 +586,7 @@ router.useOutbound('smartylighting/streetlights/1/0/action/:streetlightId/turn/o try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on','turnOnOff','subscribe'); - await smartylightingStreetlights10ActionStreetlightIdTurnOnHandler.turnOn({message}); + await smartylightingStreetlights10ActionStreetlightIdTurnOnHandler._turnOn({message}); next(); } catch (e) { @@ -422,7 +612,7 @@ router.use('smartylighting/streetlights/1/0/event/:streetlightId/lighting/measur try { await validateMessage(message.payload,'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured','lightMeasured','publish'); - await smartylightingStreetlights10EventStreetlightIdLightingMeasuredHandler.receiveLightMeasurement({message}); + await smartylightingStreetlights10EventStreetlightIdLightingMeasuredHandler._receiveLightMeasurement({message}); next(); } catch (e) { @@ -471,15 +661,36 @@ app.useOutbound(errorLogger); app.useOutbound(logger); app.useOutbound(json2string); -app - .listen() - .then((adapters) => { - console.log(cyan.underline(\`\${config.app.name} \${config.app.version}\`), gray('is ready!'), '\\\\n'); - adapters.forEach(adapter => { - console.log('🔗 ', adapter.name(), gray('is connected!')); - }); - }) - .catch(console.error); +function init() { + app + .listen() + .then((adapters) => { + console.log(cyan.underline(\`\${config.app.name} \${config.app.version}\`), gray('is ready!'), '\\\\n'); + adapters.forEach(adapter => { + console.log('🔗 ', adapter.name(), gray('is connected!')); + }); + }) + .catch(console.error); +} + +const handlers = { + registerReceiveLightMeasurementMiddleware: require('./handlers/smartylighting-streetlights-1-0-event-{streetlightId}-lighting-measured').registerReceiveLightMeasurementMiddleware, + registerTurnOnMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-turn-on').registerTurnOnMiddleware, + + registerTurnOffMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-turn-off').registerTurnOffMiddleware, + + registerDimLightMiddleware: require('./handlers/smartylighting-streetlights-1-0-action-{streetlightId}-dim').registerDimLightMiddleware, +}; + +const client = { + app, + init, + ...handlers +}; + +module.exports = { + client +}; " `; @@ -513,6 +724,7 @@ exports[`template integration tests for generated files using the generator and \\"name\\": \\"streetlights-mqtt-api\\", \\"description\\": \\"The Smartylighting Streetlights API allows you to remotely manage the city lights. ### Check out its awesome features: * Turn a specific streetlight on/off 🌃 * Dim a specific streetlight 😎 * Receive real-time information about environmental lighting conditions 📈 \\", \\"version\\": \\"1.0.0\\", + \\"main\\": \\"./src/api\\", \\"scripts\\": { \\"start\\": \\"node src/api/index.js\\" },