diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..3abcad16c
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# EditorConfig
+
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+[Makefile]
+indent_style = tab
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..ace3e5167
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,90 @@
+env:
+ node: true
+ es6: true
+
+parserOptions:
+ ecmaVersion: 2017
+
+rules:
+ # Ignore Rules
+ strict: 0
+ no-underscore-dangle: 0
+ no-mixed-requires: 0
+ no-process-exit: 0
+ no-warning-comments: 0
+ curly: 0
+ no-multi-spaces: 0
+ no-alert: 0
+ consistent-return: 0
+ consistent-this: [0, self]
+ func-style: 0
+ max-nested-callbacks: 0
+
+ # Warnings
+ no-debugger: 1
+ no-empty: 1
+ no-invalid-regexp: 1
+ no-unused-expressions: 1
+ no-native-reassign: 1
+ no-fallthrough: 1
+ camelcase: 0
+
+ # Errors
+ eqeqeq: 2
+ no-undef: 2
+ no-dupe-keys: 2
+ no-empty-character-class: 2
+ no-self-compare: 2
+ valid-typeof: 2
+ no-unused-vars: [2, { "args": "none" }]
+ handle-callback-err: 2
+ no-shadow-restricted-names: 2
+ no-new-require: 2
+ no-mixed-spaces-and-tabs: 2
+ block-scoped-var: 2
+ no-else-return: 2
+ no-throw-literal: 2
+ no-void: 2
+ radix: 2
+ wrap-iife: [2, outside]
+ no-shadow: 0
+ no-use-before-define: [2, nofunc]
+ no-path-concat: 2
+ valid-jsdoc: [0, {requireReturn: false, requireParamDescription: false, requireReturnDescription: false}]
+
+ # stylistic errors
+ no-spaced-func: 2
+ semi-spacing: 2
+ quotes: [2, 'single']
+ key-spacing: [2, { beforeColon: false, afterColon: true }]
+ indent: [2, 2]
+ no-lonely-if: 2
+ no-floating-decimal: 2
+ brace-style: [2, 1tbs, { allowSingleLine: true }]
+ comma-style: [2, last]
+ no-multiple-empty-lines: [2, {max: 1}]
+ no-nested-ternary: 2
+ operator-assignment: [2, always]
+ padded-blocks: [2, never]
+ quote-props: [2, as-needed]
+ space-before-function-paren: [2, always]
+ keyword-spacing: [2, {'before': true, 'after': true, 'overrides': {}}]
+ space-before-blocks: [2, always]
+ array-bracket-spacing: [2, never]
+ computed-property-spacing: [2, never]
+ space-in-parens: [2, never]
+ space-unary-ops: [2, {words: true, nonwords: false}]
+ wrap-regex: 2
+ linebreak-style: [2, unix]
+ semi: [2, always]
+ arrow-spacing: [2, {before: true, after: true}]
+ no-class-assign: 2
+ no-const-assign: 2
+ no-dupe-class-members: 2
+ no-this-before-super: 2
+ no-var: 2
+ object-shorthand: [2, always]
+ prefer-arrow-callback: 2
+ prefer-const: 2
+ prefer-spread: 2
+ prefer-template: 2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..052cf1489
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules
+npm-debug.log
+output
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 000000000..dc2c71c8b
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+test/
+.DS_Store
+*.swp
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..e8b399397
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+
AsyncAPI Generator
+
+ Use your AsyncAPI definition to generate literally anything. Markdown documentation, Node.js code, HTML documentation, anything!
+
+
+
+## Install
+
+```bash
+npm install -g asyncapi-generator
+```
+
+## Usage
+
+### From the command-line interface (CLI)
+
+```bash
+ Usage: ag [options]
+
+
+ Options:
+
+ -V, --version output the version number
+ -t, --templates directory where templates are located (defaults to internal templates directory)
+ -h, --help output usage information
+```
+
+#### Examples
+
+The shortest possible syntax:
+```bash
+ag asyncapi.yaml markdown
+```
+
+Specify where to put the result:
+```bash
+ag -o ./docs asyncapi.yaml markdown
+```
+
+## Requirements
+
+* Node.js v7.6+
+
+## Author
+
+Fran Mรฉndez ([@fmvilas](http://twitter.com/fmvilas))
diff --git a/cli.js b/cli.js
new file mode 100755
index 000000000..5005d1f45
--- /dev/null
+++ b/cli.js
@@ -0,0 +1,55 @@
+#!/usr/bin/env node
+
+const path = require('path');
+const program = require('commander');
+const packageInfo = require('./package.json');
+const mkdirp = require('mkdirp');
+const generate = require('./lib/generator').generate;
+
+const red = text => `\x1b[31m${text}\x1b[0m`;
+const magenta = text => `\x1b[35m${text}\x1b[0m`;
+const yellow = text => `\x1b[33m${text}\x1b[0m`;
+const green = text => `\x1b[32m${text}\x1b[0m`;
+
+let asyncapi;
+let template;
+
+const parseOutput = dir => path.resolve(dir);
+
+const showErrorAndExit = err => {
+ console.error(red('Something went wrong:'));
+ console.error(red(err.stack || err.message));
+ process.exit(1);
+};
+
+program
+ .version(packageInfo.version)
+ .arguments(' ')
+ .action((asyncAPIPath, tmpl) => {
+ asyncapi = path.resolve(asyncAPIPath);
+ template = tmpl;
+ })
+ .option('-o, --output ', 'directory where to put the generated files (defaults to current directory)', parseOutput, process.cwd())
+ .option('-t, --templates ', 'directory where templates are located (defaults to internal templates directory)')
+ .parse(process.argv);
+
+if (!asyncapi) {
+ console.error(red('> Path to AsyncAPI file not provided.'));
+ program.help(); // This exits the process
+}
+
+mkdirp(program.output, err => {
+ if (err) return showErrorAndExit(err);
+
+ generate({
+ asyncapi,
+ target_dir: program.output,
+ template,
+ templates: program.templates,
+ }).then(() => {
+ console.log(green('Done! โจ'));
+ console.log(yellow('Check out your shiny new API documentation at ') + magenta(program.output) + yellow('.'));
+ }).catch(showErrorAndExit);
+});
+
+process.on('unhandledRejection', showErrorAndExit);
diff --git a/lib/beautifier.js b/lib/beautifier.js
new file mode 100644
index 000000000..e25e79638
--- /dev/null
+++ b/lib/beautifier.js
@@ -0,0 +1,162 @@
+const OpenAPISampler = require('openapi-sampler');
+const _ = require('lodash');
+const md = require('markdown-it')();
+
+const sharedStart = (array) => {
+ const A = array.concat().sort();
+ const a1 = A[0], a2= A[A.length-1], L= a1.length;
+ let i = 0;
+ while (i {
+ if (schema.allOf) {
+ const schemas = [];
+ schema.allOf.forEach((s) => {
+ schemas.push(s);
+ });
+
+ return resolveAllOf(_.merge({}, ...schemas));
+ }
+
+ if (schema.properties) {
+ const transformed = {};
+
+ for (const key in schema.properties) {
+ if (schema.properties[key].allOf) {
+ transformed[key] = resolveAllOf(schema.properties[key]);
+ continue;
+ }
+ transformed[key] = schema.properties[key];
+ }
+
+ return {
+ ...schema,
+ ...{ properties: transformed }
+ };
+ }
+
+ return schema;
+};
+
+const stringify = json => JSON.stringify(json || '', null, 2);
+const generateExample = schema => OpenAPISampler.sample(schema);
+
+const beautifyMessage = (message) => {
+ if (message.payload) message.payload = resolveAllOf(message.payload);
+ if (message.headers) message.headers = resolveAllOf(message.headers);
+ message.summaryAsHTML = md.render(message.summary || '');
+ message.descriptionAsHTML = md.render(message.description || '');
+
+ if (message.headers) {
+ beautifySchema(message.headers);
+ message.generatedHeadersExample = stringify(generateExample(message.headers));
+ }
+ if (message.payload) {
+ beautifySchema(message.payload);
+ if (message.payload.example) {
+ message.formattedExample = stringify(message.payload.example);
+ } else {
+ message.generatedPayloadExample = stringify(generateExample(message.payload));
+ }
+ }
+
+ return message;
+};
+
+const beautifySchema = (schema) => {
+ _.each(schema.properties, (prop, propName) => {
+ if (prop.description) prop.descriptionAsHTML = md.render(prop.description || '');
+ if (prop.properties) beautifySchema(prop);
+ });
+ _.each(schema.additionalProperties, (prop, propName) => {
+ if (prop.description) prop.descriptionAsHTML = md.render(prop.description || '');
+ if (prop.additionalProperties) beautifySchema(prop);
+ });
+};
+
+module.exports = (asyncapi) => {
+ asyncapi.baseTopic = asyncapi.baseTopic || '';
+ asyncapi.info = asyncapi.info || {};
+ asyncapi.info.descriptionAsHTML = md.render(asyncapi.info.description || '');
+
+ if (asyncapi.servers) {
+ _.each(asyncapi.servers, server => {
+ server.descriptionAsHTML = md.render(server.description || '');
+ _.each(server.variables, variable => {
+ variable.descriptionAsHTML = md.render(variable.description || '');
+ });
+ });
+ }
+
+ if (asyncapi.security) {
+ asyncapi._security = [];
+
+ _.each(asyncapi.security, security => {
+ const name = Object.keys(security)[0];
+ if (!asyncapi.components || !asyncapi.components.securitySchemes || !asyncapi.components.securitySchemes[name]) {
+ throw new Error(`Security definition "${name}" is not in included in #/components/securitySchemes.`);
+ }
+
+ asyncapi.components.securitySchemes[name].descriptionAsHTML = md.render(asyncapi.components.securitySchemes[name].description || '');
+ asyncapi._security.push(asyncapi.components.securitySchemes[name]);
+ });
+ }
+
+ _.each(asyncapi.topics, (topic, topicName) => {
+ const separator = asyncapi['x-topic-separator'] || '.';
+ const baseTopic = asyncapi.baseTopic.trim();
+
+ const newTopicName = baseTopic.length ? `${baseTopic}${separator}${topicName}` : topicName;
+ if (newTopicName !== topicName) {
+ asyncapi.topics[newTopicName] = topic;
+ delete asyncapi.topics[topicName];
+ }
+
+ if (topic.publish) {
+ beautifyMessage(topic.publish);
+ if (topic.publish.oneOf) _.each(topic.publish.oneOf, beautifyMessage);
+ }
+
+ if (topic.subscribe) {
+ beautifyMessage(topic.subscribe);
+ if (topic.subscribe.oneOf) _.each(topic.subscribe.oneOf, beautifyMessage);
+ }
+
+ if (topic.parameters) {
+ topic.parameters.forEach((param) => {
+ param.descriptionAsHTML = md.render(param.description || '');
+ });
+ }
+ });
+
+ if (!asyncapi.components) asyncapi.components = {};
+ if (!asyncapi.components.messages) {
+ asyncapi.__noMessages = true;
+ asyncapi.components.messages = {};
+ }
+ if (!asyncapi.components.schemas) {
+ asyncapi.__noSchemas = true;
+ asyncapi.components.schemas = {};
+ }
+
+ _.each(asyncapi.components.messages, beautifyMessage);
+
+ _.each(asyncapi.components.schemas, (schema, schemaName) => {
+ schema = resolveAllOf(schema);
+ if (schema.example) {
+ schema.formattedExample = stringify(schema.example);
+ } else {
+ schema.generatedExample = stringify(generateExample(schema));
+ }
+ asyncapi.components.schemas[schemaName] = schema;
+ beautifySchema(schema);
+ });
+
+ const commonPrefix = sharedStart(Object.keys(asyncapi.topics));
+ const levels = commonPrefix.split('.').length - 1;
+ asyncapi.__commonPrefix = commonPrefix.split('.').slice(0, levels).join('.');
+
+ return asyncapi;
+};
diff --git a/lib/generator.js b/lib/generator.js
new file mode 100644
index 000000000..c4c33f28d
--- /dev/null
+++ b/lib/generator.js
@@ -0,0 +1,301 @@
+const os = require('os');
+const path = require('path');
+const fs = require('fs');
+const Handlebars = require('handlebars');
+const _ = require('lodash');
+const randomName = require('project-name-generator');
+const xfs = require('fs.extra');
+const beautifier = require('./beautifier');
+const registerPartial = require('./register-partial');
+const parse = require('./parser');
+
+const generator = module.exports;
+
+/**
+ * Generates a file.
+ *
+ * @private
+ * @param {Object} options
+ * @param {String} options.templates_dir Directory where the templates live.
+ * @param {String} options.target_dir Directory where the file will be generated.
+ * @param {String} options.file_name Name of the generated file.
+ * @param {String} options.root Root directory.
+ * @param {Object} options.data Data to pass to the template.
+ * @return {Promise}
+ */
+const generateFile = options => new Promise((resolve, reject) => {
+ const templates_dir = options.templates_dir;
+ const target_dir = options.target_dir;
+ const file_name = options.file_name;
+ const root = options.root;
+ const data = options.data;
+
+ fs.readFile(path.resolve(root, file_name), 'utf8', (err, content) => {
+ if (err) return reject(err);
+
+ try {
+ const template = Handlebars.compile(content);
+ const parsed_content = template(data);
+ const template_path = path.relative(templates_dir, path.resolve(root, file_name));
+ const generated_path = path.resolve(target_dir, template_path);
+
+ fs.writeFile(generated_path, parsed_content, 'utf8', (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ } catch (e) {
+ reject(e);
+ }
+ });
+});
+
+/**
+ * Generates a file for every topic.
+ *
+ * @param config
+ * @param topic
+ * @param topicName
+ * @returns {Promise}
+ */
+const generateTopicFile = (config, topic, topicName) => new Promise((resolve, reject) => {
+ fs.readFile(path.join(config.root, config.file_name), 'utf8', (err, data) => {
+ if (err) return reject(err);
+ const subdir = config.root.replace(new RegExp(`${config.templates_dir}[/]?`),'');
+ const new_filename = config.file_name.replace('$$topic$$', topicName);
+ const target_file = path.resolve(config.target_dir, subdir, new_filename);
+ const template = Handlebars.compile(data.toString());
+ const content = template({
+ openbrace: '{',
+ closebrace: '}' ,
+ topicName: topicName.replace(/[}{]/g, ''),
+ topic,
+ asyncapi: config.data.asyncapi
+ });
+
+ fs.writeFile(target_file, content, 'utf8', (err) => {
+ if (err) return reject(err);
+ resolve();
+ });
+ });
+});
+
+/**
+ * Generates all the files for each topic.
+ *
+ * @param {Object} config Configuration options
+ * @returns {Promise}
+ */
+const generateTopicFiles = options => new Promise((resolve, reject) => {
+ let shouldResolve = true;
+
+ _.each(options.data.asyncapi.topics, (topic, topicName) => {
+ generateTopicFile(options, topic, topicName).catch((err) => {
+ shouldResolve = false;
+ reject(err);
+ });
+ });
+
+ if (shouldResolve) resolve();
+});
+
+/**
+ * Generates the directory structure.
+ *
+ * @private
+ * @param {Object} config Configuration options
+ * @param {Object|String} config.asyncapi AsyncAPI JSON or a string pointing to a AsyncAPI file.
+ * @param {String} config.target_dir Absolute path to the directory where the files will be generated.
+ * @param {String} config.templates Absolute path to the templates that should be used.
+ * @return {Promise}
+ */
+const generateDirectoryStructure = config => new Promise((resolve, reject) => {
+ const target_dir = config.target_dir;
+ const templates_dir = config.templates;
+
+ xfs.mkdirpSync(target_dir);
+
+ const walker = xfs.walk(templates_dir, {
+ followLinks: false
+ });
+
+ walker.on('file', async (root, stats, next) => {
+ try {
+ if (stats.name.includes('$$topic$$')) {
+ // this file should be handled for each in asyncapi.paths
+ await generateTopicFiles({
+ root,
+ templates_dir,
+ target_dir,
+ data: config,
+ file_name: stats.name
+ });
+ const template_path = path.relative(templates_dir, path.resolve(root, stats.name));
+ fs.unlink(path.resolve(target_dir, template_path), next);
+ } else {
+ const file_path = path.relative(templates_dir, path.resolve(root, stats.name));
+ if (!file_path.startsWith('.partials/') && !file_path.startsWith('.helpers/')) {
+ // this file should only exist once.
+ await generateFile({
+ root,
+ templates_dir,
+ target_dir,
+ data: config,
+ file_name: stats.name
+ });
+ }
+ next();
+ }
+ } catch (e) {
+ reject(e);
+ }
+ });
+
+ walker.on('directory', async (root, stats, next) => {
+ try {
+ const dir_path = path.resolve(target_dir, path.relative(templates_dir, path.resolve(root, stats.name)));
+ if (stats.name !== '.partials' && stats.name !== '.helpers') xfs.mkdirpSync(dir_path);
+ next();
+ } catch (e) {
+ reject(e);
+ }
+ });
+
+ walker.on('errors', (root, nodeStatsArray) => {
+ reject(nodeStatsArray);
+ });
+
+ walker.on('end', async () => {
+ resolve();
+ });
+});
+
+/**
+ * Register the template partials
+ *
+ * @private
+ * @param {Object} config Configuration options
+ * @param {String} config.templates Absolute path to the templates that should be used.
+ * @return {Promise}
+ */
+const registerHelpers = config => new Promise((resolve, reject) => {
+ const helpers_dir = path.resolve(config.templates, '.helpers');
+
+ if (!fs.existsSync(helpers_dir)) return resolve();
+
+ const walker = xfs.walk(helpers_dir, {
+ followLinks: false
+ });
+
+ walker.on('file', async (root, stats, next) => {
+ try {
+ const file_path = path.resolve(config.templates, path.resolve(root, stats.name));
+ require(file_path);
+ next();
+ } catch (e) {
+ reject(e);
+ }
+ });
+
+ walker.on('errors', (root, nodeStatsArray) => {
+ reject(nodeStatsArray);
+ });
+
+ walker.on('end', async () => {
+ resolve();
+ });
+});
+
+/**
+ * Register the template helpers
+ *
+ * @private
+ * @param {Object} config Configuration options
+ * @param {String} config.templates Absolute path to the templates that should be used.
+ * @return {Promise}
+ */
+const registerPartials = config => new Promise((resolve, reject) => {
+ const partials_dir = path.resolve(config.templates, '.partials');
+
+ if (!fs.existsSync(partials_dir)) return resolve();
+
+ const walker = xfs.walk(partials_dir, {
+ followLinks: false
+ });
+
+ walker.on('file', async (root, stats, next) => {
+ try {
+ const file_path = path.resolve(config.templates, path.resolve(root, stats.name));
+ await registerPartial(file_path);
+ next();
+ } catch (e) {
+ reject(e);
+ }
+ });
+
+ walker.on('errors', (root, nodeStatsArray) => {
+ reject(nodeStatsArray);
+ });
+
+ walker.on('end', () => {
+ resolve();
+ });
+});
+
+const bundle = async (asyncapi) => {
+ if (typeof asyncapi === 'string') {
+ try {
+ return await parse(asyncapi);
+ } catch (e) {
+ throw e;
+ }
+ } else if (typeof asyncapi !== 'object') {
+ throw new Error(`Could not find a valid asyncapi definition: ${asyncapi}`);
+ }
+
+ return asyncapi;
+};
+
+/**
+ * Outputs the result of compiling a template.
+ *
+ * @module generator.generate
+ * @param {Object} config Configuration options
+ * @param {Object|String} config.asyncapi AsyncAPI JSON or a string pointing to an AsyncAPI file.
+ * @param {String} config.target_dir Path to the directory where the files will be generated.
+ * @return {Promise}
+ */
+generator.generate = config => new Promise((resolve, reject) => {
+ bundle(config.asyncapi)
+ .then((asyncapi) => {
+ config.asyncapi = beautifier(asyncapi);
+ const random_name = randomName().dashed;
+
+ if (!config.template) {
+ return reject(new Error('No template has been specified.'));
+ }
+
+ _.defaultsDeep(config, {
+ asyncapi: {
+ info: {
+ title: random_name
+ }
+ },
+ package: {
+ name: _.kebabCase(_.result(config, 'asyncapi.info.title', random_name))
+ },
+ target_dir: path.resolve(os.tmpdir(), 'asyncapi-generated'),
+ templates: path.resolve(__dirname, '../templates')
+ });
+
+ config.templates = `${config.templates}/${config.template}`;
+
+ async function start () {
+ await registerHelpers(config);
+ await registerPartials(config);
+ await generateDirectoryStructure(config);
+ }
+
+ start().then(resolve).catch(reject);
+ })
+ .catch(reject);
+});
diff --git a/lib/parser.js b/lib/parser.js
new file mode 100644
index 000000000..69d3b9dba
--- /dev/null
+++ b/lib/parser.js
@@ -0,0 +1,114 @@
+const fs = require('fs');
+const path = require('path');
+const ZSchema = require('z-schema');
+const YAML = require('js-yaml');
+const RefParser = require('json-schema-ref-parser');
+
+const validator = new ZSchema();
+
+async function getFileContent (filePath) {
+ return new Promise((resolve, reject) => {
+ fs.readFile(path.resolve(__dirname, filePath), (err, content) => {
+ if (err) return reject(err);
+ resolve(content);
+ });
+ });
+}
+
+function parseContent (content) {
+ content = content.toString('utf8');
+ try {
+ return JSON.parse(content);
+ } catch (e) {
+ return YAML.safeLoad(content);
+ }
+}
+
+async function dereference (json) {
+ return RefParser.dereference(json, {
+ dereference: {
+ circular: 'ignore'
+ }
+ });
+}
+
+async function bundle (json) {
+ return RefParser.bundle(json, {
+ dereference: {
+ circular: 'ignore'
+ }
+ });
+}
+
+async function validate (json, schema) {
+ return new Promise((resolve, reject) => {
+ validator.validate(json, schema, (err, valid) => {
+ if (err) return reject(err);
+ return resolve(json);
+ });
+ });
+}
+
+async function parse (filePath) {
+ let content, parsedContent, dereferencedJSON, bundledJSON, parsed;
+
+ try {
+ content = await getFileContent(filePath);
+ } catch (e) {
+ console.error('Can not load the content of the AsyncAPI specification file');
+ console.error(e);
+ return;
+ }
+
+ try {
+ parsedContent = parseContent(content);
+ } catch (e) {
+ console.error('Can not parse the content of the AsyncAPI specification file');
+ console.error(e);
+ return;
+ }
+
+ try {
+ dereferencedJSON = await dereference(parsedContent);
+ } catch (e) {
+ console.error('Can not dereference the JSON obtained from the content of the AsyncAPI specification file');
+ console.error(e);
+ return;
+ }
+
+ try {
+ bundledJSON = await bundle(dereferencedJSON);
+ } catch (e) {
+ console.error('Can not bundle the JSON obtained from the content of the AsyncAPI specification file');
+ console.error(e);
+ return;
+ }
+
+ try {
+ const asyncAPIschema = require('asyncapi')[bundledJSON.asyncapi];
+ parsed = await validate(bundledJSON, asyncAPIschema);
+ } catch (e) {
+ console.error('Invalid JSON obtained from the content of the AsyncAPI specification file');
+ console.error(e);
+ return;
+ }
+
+ return JSON.parse(JSON.stringify(parsed));
+};
+
+async function parseText (content) {
+ const parsedContent = parseContent(content);
+ if (typeof parsedContent !== 'object' || parsedContent === null) {
+ throw new Error('Invalid YAML content.');
+ }
+ const dereferencedJSON = await dereference(parsedContent);
+ const bundledJSON = await bundle(dereferencedJSON);
+ const asyncAPIschema = require('asyncapi')[bundledJSON.asyncapi];
+
+ const parsed = await validate(bundledJSON, asyncAPIschema);
+
+ return JSON.parse(JSON.stringify(parsed));
+};
+
+module.exports = parse;
+module.exports.parseText = parseText;
diff --git a/lib/register-partial.js b/lib/register-partial.js
new file mode 100644
index 000000000..f66be0144
--- /dev/null
+++ b/lib/register-partial.js
@@ -0,0 +1,13 @@
+const path = require('path');
+const fs = require('fs');
+const _ = require('lodash');
+const Handlebars = require('handlebars');
+
+const getFileContent = filePath => {
+ return fs.readFileSync(filePath, 'utf8');
+};
+
+module.exports = async filePath => {
+ const partialName = _.camelCase(path.basename(filePath, path.extname(filePath)));
+ Handlebars.registerPartial(partialName, getFileContent(filePath));
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 000000000..5c382b3b6
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,441 @@
+{
+ "name": "asyncapi-markdown",
+ "version": "0.1.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "align-text": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
+ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+ "requires": {
+ "kind-of": "3.2.2",
+ "longest": "1.0.1",
+ "repeat-string": "1.6.1"
+ }
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "requires": {
+ "sprintf-js": "1.0.3"
+ }
+ },
+ "async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
+ },
+ "asyncapi": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/asyncapi/-/asyncapi-2.0.2.tgz",
+ "integrity": "sha512-iKgKWs2Ox+f9pFvnRjtnHOPXo5hU4GhkWK44Rw6lKYn/Um9eEx50VCayjCJBNT3cvzn7CSVFMZiioRAFwRGz4g=="
+ },
+ "call-me-maybe": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
+ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms="
+ },
+ "camelcase": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+ "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
+ "optional": true
+ },
+ "center-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+ "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
+ "optional": true,
+ "requires": {
+ "align-text": "0.1.4",
+ "lazy-cache": "1.0.4"
+ }
+ },
+ "cliui": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+ "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
+ "optional": true,
+ "requires": {
+ "center-align": "0.1.3",
+ "right-align": "0.1.3",
+ "wordwrap": "0.0.2"
+ },
+ "dependencies": {
+ "wordwrap": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+ "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
+ "optional": true
+ }
+ }
+ },
+ "commander": {
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz",
+ "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew=="
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "optional": true
+ },
+ "entities": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
+ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+ },
+ "foreach": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
+ "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
+ },
+ "foreachasync": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz",
+ "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY="
+ },
+ "format-util": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz",
+ "integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU="
+ },
+ "fs-extra": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz",
+ "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=",
+ "requires": {
+ "jsonfile": "1.0.1",
+ "mkdirp": "0.3.5",
+ "ncp": "0.4.2",
+ "rimraf": "2.2.8"
+ },
+ "dependencies": {
+ "mkdirp": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
+ "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
+ }
+ }
+ },
+ "fs.extra": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz",
+ "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=",
+ "requires": {
+ "fs-extra": "0.6.4",
+ "mkdirp": "0.3.5",
+ "walk": "2.3.14"
+ },
+ "dependencies": {
+ "mkdirp": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz",
+ "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc="
+ }
+ }
+ },
+ "handlebars": {
+ "version": "4.0.11",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz",
+ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=",
+ "requires": {
+ "async": "1.5.2",
+ "optimist": "0.6.1",
+ "source-map": "0.4.4",
+ "uglify-js": "2.8.29"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+ },
+ "js-yaml": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
+ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
+ "requires": {
+ "argparse": "1.0.10",
+ "esprima": "4.0.1"
+ }
+ },
+ "json-pointer": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.0.tgz",
+ "integrity": "sha1-jlAFUKaqxUZKRzN32leqbMIoKNc=",
+ "requires": {
+ "foreach": "2.0.5"
+ }
+ },
+ "json-schema-ref-parser": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.0.tgz",
+ "integrity": "sha512-MEsbUWCUpalA2UJM6dO4sOI7wIn122pkXAFdmJgJQwbssLA7n3Kr2+v7Z9Oam6q02JNlE0AmRs0GbytijLKO5Q==",
+ "requires": {
+ "call-me-maybe": "1.0.1",
+ "debug": "3.1.0",
+ "js-yaml": "3.12.0",
+ "ono": "4.0.5"
+ }
+ },
+ "jsonfile": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz",
+ "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0="
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "requires": {
+ "is-buffer": "1.1.6"
+ }
+ },
+ "lazy-cache": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+ "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
+ "optional": true
+ },
+ "linkify-it": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz",
+ "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=",
+ "requires": {
+ "uc.micro": "1.0.5"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+ "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
+ },
+ "lodash.get": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
+ },
+ "lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
+ },
+ "longest": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
+ "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+ },
+ "markdown-it": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.1.tgz",
+ "integrity": "sha512-CzzqSSNkFRUf9vlWvhK1awpJreMRqdCrBvZ8DIoDWTOkESMIF741UPAhuAmbyWmdiFPA6WARNhnu2M6Nrhwa+A==",
+ "requires": {
+ "argparse": "1.0.10",
+ "entities": "1.1.1",
+ "linkify-it": "2.0.3",
+ "mdurl": "1.0.1",
+ "uc.micro": "1.0.5"
+ }
+ },
+ "mdurl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
+ "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4="
+ },
+ "minimist": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "requires": {
+ "minimist": "0.0.8"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "ncp": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz",
+ "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ="
+ },
+ "ono": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.5.tgz",
+ "integrity": "sha512-ZVNuV9kJbr/2tWs83I2snrYo+WIS0DISF/xUfX9p9b6GyDD6F5N9PzHjW+p/dep6IGwSYylf1HCub5I/nM0R5Q==",
+ "requires": {
+ "format-util": "1.0.3"
+ }
+ },
+ "openapi-sampler": {
+ "version": "1.0.0-beta.13",
+ "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.0.0-beta.13.tgz",
+ "integrity": "sha512-NReKJh92NlUmY9CDo86L1Skkx3DMDeWmiLoqMxQSX7xBpoSx8WJ1JdTb7nEwz6M7S+WDPrQsEfS5BaIW27nMrQ==",
+ "requires": {
+ "json-pointer": "0.6.0"
+ }
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "requires": {
+ "minimist": "0.0.10",
+ "wordwrap": "0.0.3"
+ }
+ },
+ "project-name-generator": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/project-name-generator/-/project-name-generator-2.1.5.tgz",
+ "integrity": "sha512-deZX2f9ijkheLjCBaM5lGYgWnddQYxqaby6szdv0R6YlsmmEWz8tYyFq8B9BTYCZbXHsBS9cWTSqkAa4tSA1RA==",
+ "requires": {
+ "commander": "2.16.0",
+ "lodash": "4.17.10"
+ }
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
+ },
+ "right-align": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+ "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
+ "optional": true,
+ "requires": {
+ "align-text": "0.1.4"
+ }
+ },
+ "rimraf": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
+ "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
+ },
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "requires": {
+ "amdefine": "1.0.1"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+ },
+ "uc.micro": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.5.tgz",
+ "integrity": "sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg=="
+ },
+ "uglify-js": {
+ "version": "2.8.29",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
+ "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
+ "optional": true,
+ "requires": {
+ "source-map": "0.5.7",
+ "uglify-to-browserify": "1.0.2",
+ "yargs": "3.10.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "optional": true
+ }
+ }
+ },
+ "uglify-to-browserify": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
+ "optional": true
+ },
+ "validator": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-10.4.0.tgz",
+ "integrity": "sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg=="
+ },
+ "walk": {
+ "version": "2.3.14",
+ "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz",
+ "integrity": "sha512-5skcWAUmySj6hkBdH6B6+3ddMjVQYH5Qy9QGbPmN8kVmLteXk+yVXg+yfk1nbX30EYakahLrr8iPcCxJQSCBeg==",
+ "requires": {
+ "foreachasync": "3.0.0"
+ }
+ },
+ "window-size": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
+ "optional": true
+ },
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+ },
+ "yargs": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+ "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
+ "optional": true,
+ "requires": {
+ "camelcase": "1.2.1",
+ "cliui": "2.1.0",
+ "decamelize": "1.2.0",
+ "window-size": "0.1.0"
+ }
+ },
+ "z-schema": {
+ "version": "3.22.0",
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.22.0.tgz",
+ "integrity": "sha512-Oq82unxX2PTcJ031gFGcksDHE5PNBs5CbcQ1tbre0Sl4Mu5habZTVmEAkuZS4cK//VgIdNg9UG9PMgMlN6KmiA==",
+ "requires": {
+ "commander": "2.16.0",
+ "lodash.get": "4.4.2",
+ "lodash.isequal": "4.5.0",
+ "validator": "10.4.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..41b80a1c9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "asyncapi-generator",
+ "version": "0.1.0",
+ "description": "The AsyncAPI generator. It can generate documentation, code, anything!",
+ "main": "./lib/generator.js",
+ "bin": {
+ "asyncapi-generator": "./cli.js",
+ "ag": "./cli.js"
+ },
+ "preferGlobal": true,
+ "bugs": {
+ "url": "https://github.com/asyncapi/generator/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/asyncapi/generator.git"
+ },
+ "keywords": [
+ "asyncapi",
+ "documentation",
+ "generator",
+ "markdown"
+ ],
+ "author": {
+ "name": "Fran Mendez",
+ "email": "fmvilas@gmail.com"
+ },
+ "homepage": "https://github.com/asyncapi/generator",
+ "dependencies": {
+ "asyncapi": "^2.0.2",
+ "commander": "^2.12.2",
+ "fs.extra": "^1.3.2",
+ "handlebars": "^4.0.6",
+ "js-yaml": "^3.8.3",
+ "json-schema-ref-parser": "^5.1.0",
+ "lodash": "^4.17.4",
+ "markdown-it": "^8.4.1",
+ "mkdirp": "^0.5.1",
+ "openapi-sampler": "^1.0.0-beta.9",
+ "project-name-generator": "^2.1.5",
+ "z-schema": "^3.18.2"
+ },
+ "devDependencies": {}
+}
diff --git a/templates/markdown/$$topic$$-topic.md b/templates/markdown/$$topic$$-topic.md
new file mode 100644
index 000000000..2a8d36c78
--- /dev/null
+++ b/templates/markdown/$$topic$$-topic.md
@@ -0,0 +1,2 @@
+## {{{topic.publish.descriptionAsHTML}}}
+## {{{topic.subscribe.descriptionAsHTML}}}
diff --git a/templates/markdown/.helpers/handlebars.js b/templates/markdown/.helpers/handlebars.js
new file mode 100644
index 000000000..e4d0bffd9
--- /dev/null
+++ b/templates/markdown/.helpers/handlebars.js
@@ -0,0 +1,46 @@
+const Handlebars = require('handlebars');
+
+Handlebars.registerHelper('concat', (str1, str2, separator) => {
+ return `${str1 || ''}${separator || ''}${str2 || ''}`;
+});
+
+Handlebars.registerHelper('tree', path => {
+ const filteredPaths = path.split('.').filter(Boolean);
+ if (!filteredPaths.length) return;
+ const dottedPath = filteredPaths.join('.');
+
+ return `${dottedPath}.`;
+});
+
+Handlebars.registerHelper('buildPath', (propName, path, key) => {
+ if (!path) return propName;
+ return `${path}.${propName}`;
+});
+
+Handlebars.registerHelper('isRequired', (obj, key) => {
+ return obj && obj.required && !!(obj.required.includes(key));
+});
+
+Handlebars.registerHelper('acceptedValues', items => {
+ if (!items) return 'Any';
+
+ return items.map(i => `${i}
`).join(', ');
+});
+
+Handlebars.registerHelper('equal', (lvalue, rvalue, options) => {
+ if (arguments.length < 3)
+ throw new Error('Handlebars Helper equal needs 2 parameters');
+ if (lvalue!==rvalue) {
+ return options.inverse(this);
+ }
+
+ return options.fn(this);
+});
+
+Handlebars.registerHelper('inc', (number) => {
+ return number + 1;
+});
+
+Handlebars.registerHelper('log', (something) => {
+ console.log(require('util').inspect(something, { depth: null }));
+});
diff --git a/templates/markdown/.partials/content.md b/templates/markdown/.partials/content.md
new file mode 100644
index 000000000..fdf592440
--- /dev/null
+++ b/templates/markdown/.partials/content.md
@@ -0,0 +1,9 @@
+{{> info}}
+
+{{> security}}
+
+{{> topics }}
+
+{{> messages}}
+
+{{> schemas}}
diff --git a/templates/markdown/.partials/info.md b/templates/markdown/.partials/info.md
new file mode 100644
index 000000000..cb5d735ea
--- /dev/null
+++ b/templates/markdown/.partials/info.md
@@ -0,0 +1,79 @@
+{{#if asyncapi.info.termsOfService}}
+
+## Terms of service
+[{{asyncapi.info.termsOfService}}]({{asyncapi.info.termsOfService}})
+{{/if}}
+
+{{#if asyncapi.servers}}
+
+## Connection details
+
+
+
+
+ URL |
+ Scheme |
+ Description |
+
+
+
+ {{#each asyncapi.servers as |server|}}
+
+ {{#if server.variables}}{{/if}}{{server.url}} |
+ {{server.scheme}} |
+ {{{server.description}}} |
+
+
+ {{#if server.variables}}
+
+
+
+ Show more
+
+
+
+
+
+
+ Name |
+ Default value |
+ Possible values |
+ Description |
+
+
+
+ {{#each server.variables as |var|}}
+
+ {{@key}} |
+
+ {{#if var.default}}
+ {{var.default}}
+ {{else}}
+ None
+ {{/if}}
+ |
+
+ {{#if var.enum}}
+
+ {{#each var.enum as |value|}}
+ - {{value}}
+ {{/each}}
+
+ {{else}}
+ Any
+ {{/if}}
+ |
+ {{{var.description}}} |
+
+ {{/each}}
+
+
+
+ |
+
+ {{/if}}
+ {{/each}}
+
+
+
+{{/if}}
diff --git a/templates/markdown/.partials/message.md b/templates/markdown/.partials/message.md
new file mode 100644
index 000000000..7b82fa105
--- /dev/null
+++ b/templates/markdown/.partials/message.md
@@ -0,0 +1,55 @@
+
+{{#if message.summary}}
+{{{message.summary}}}
+
+{{/if}}
+{{#if message.description}}
+{{{message.description}}}
+{{/if}}
+
+{{#if message.headers}}
+#### Headers
+
+{{> schema schema=message.headers schemaName='Message Headers' hideTitle=true}}
+{{/if}}
+
+{{#unless message.headers.example}}
+{{#if message.generatedHeadersExample}}
+##### Example of headers _(generated)_
+
+```json
+{{{message.generatedHeadersExample}}}
+```
+{{/if}}
+{{/unless}}
+
+#### Payload
+
+{{> schema schema=message.payload schemaName='Message Payload' hideTitle=true}}
+
+{{#if message.payload.example}}
+##### Example
+
+```json
+{{{message.formattedExample}}}
+```
+{{else}}
+{{#if message.generatedPayloadExample}}
+##### Example of payload _(generated)_
+
+```json
+{{{message.generatedPayloadExample}}}
+```
+{{/if}}
+{{/if}}
+
+{{#if operation.tags}}
+Tags
+
+{{> tags tags=operation.tags}}
+{{/if}}
diff --git a/templates/markdown/.partials/messages.md b/templates/markdown/.partials/messages.md
new file mode 100644
index 000000000..7dfcfbe69
--- /dev/null
+++ b/templates/markdown/.partials/messages.md
@@ -0,0 +1,5 @@
+## Messages
+
+{{#each asyncapi.components.messages}}
+{{~>message message=. messageName=@key~}}
+{{/each}}
diff --git a/templates/markdown/.partials/operation.md b/templates/markdown/.partials/operation.md
new file mode 100644
index 000000000..06b281675
--- /dev/null
+++ b/templates/markdown/.partials/operation.md
@@ -0,0 +1,50 @@
+{{#if operation.summary}}
+{{{operation.summary}}}
+
+{{/if}}
+{{#if operation.description}}
+{{{operation.description}}}
+{{/if}}
+
+{{#if operation.headers}}
+##### Headers
+
+{{> schema schema=operation.headers schemaName='Message Headers' hideTitle=true}}
+
+{{#unless operation.example}}
+{{#if operation.generatedHeadersExample}}
+###### Example of headers _(generated)_
+
+```json
+{{{operation.generatedHeadersExample}}}
+```
+{{/if}}
+{{/unless}}
+{{/if}}
+
+##### Payload
+
+{{> schema schema=operation.payload schemaName='Message Payload' hideTitle=true}}
+
+{{#if operation.payload.example}}
+###### Example
+
+```json
+{{{operation.formattedExample}}}
+```
+{{else}}
+{{#if operation.generatedPayloadExample}}
+###### Example of payload _(generated)_
+
+```json
+{{{operation.generatedPayloadExample}}}
+```
+{{/if}}
+{{/if}}
+
+{{#if operation.tags}}
+##### Tags
+
+{{> tags tags=operation.tags}}
+{{/if}}
+
diff --git a/templates/markdown/.partials/parameters.md b/templates/markdown/.partials/parameters.md
new file mode 100644
index 000000000..8af58059e
--- /dev/null
+++ b/templates/markdown/.partials/parameters.md
@@ -0,0 +1,15 @@
+{{#unless hideTitle}}
+#### Topic Parameters
+{{/unless}}
+
+{{#each params as |param|}}
+{{#if param.name}}
+##### {{param.name}}
+{{/if}}
+
+{{#if param.description}}
+{{{param.description}}}
+{{/if}}
+
+{{~> schema schema=param.schema schemaName=param.name hideTitle=true ~}}
+{{/each}}
diff --git a/templates/markdown/.partials/schema-prop.md b/templates/markdown/.partials/schema-prop.md
new file mode 100644
index 000000000..bef072f10
--- /dev/null
+++ b/templates/markdown/.partials/schema-prop.md
@@ -0,0 +1,26 @@
+
+ {{tree path}}{{propName}} {{#if required}}(required){{/if}} |
+
+ {{prop.type}}
+ {{~#if prop.anyOf}}anyOf{{~/if}}
+ {{~#if prop.oneOf}}oneOf{{~/if}}
+ {{~#if prop.items.type}}({{prop.items.type}}){{~/if}}
+ |
+ {{{prop.descriptionAsHTML}}} |
+ {{{acceptedValues prop.enum}}} |
+
+{{#each prop.anyOf}}
+{{> schemaProp prop=. propName=@key path=(buildPath ../propName ../path @key)}}
+{{/each}}
+{{#each prop.oneOf}}
+ {{> schemaProp prop=. propName=@key path=(buildPath ../propName ../path @key)}}
+{{/each}}
+{{#each prop.properties}}
+{{> schemaProp prop=. propName=@key required=(isRequired ../prop @key) path=(buildPath ../propName ../path @key)}}
+{{/each}}
+{{#each prop.additionalProperties.properties}}
+{{> schemaProp prop=. propName=@key required=(isRequired ../prop.additionalProperties @key) path=(buildPath ../propName ../path @key)}}
+{{/each}}
+{{#each prop.items.properties}}
+{{> schemaProp prop=. propName=@key required=(isRequired ../prop.items @key) path=(buildPath ../propName ../path @key)}}
+{{/each}}
diff --git a/templates/markdown/.partials/schema.md b/templates/markdown/.partials/schema.md
new file mode 100644
index 000000000..01f8a156e
--- /dev/null
+++ b/templates/markdown/.partials/schema.md
@@ -0,0 +1,37 @@
+{{#unless hideTitle}}
+#### {{schemaName}}
+{{/unless}}
+
+
+
+
+ Name |
+ Type |
+ Description |
+ Accepted values |
+
+
+
+ {{#each schema.properties}}
+ {{> schemaProp prop=. propName=@key required=(isRequired ../schema @key) path=''}}
+ {{else}}
+ {{> schemaProp prop=schema propName=schemaName required=(isRequired ../schema @key) path=''}}
+ {{/each}}
+
+
+
+{{#if schema.formattedExample}}
+##### Example
+
+```json
+{{{schema.formattedExample}}}
+```
+{{else}}
+{{#if schema.generatedExample}}
+##### Example _(generated)_
+
+```json
+{{{schema.generatedExample}}}
+```
+{{/if}}
+{{/if}}
diff --git a/templates/markdown/.partials/schemas.md b/templates/markdown/.partials/schemas.md
new file mode 100644
index 000000000..55220809c
--- /dev/null
+++ b/templates/markdown/.partials/schemas.md
@@ -0,0 +1,7 @@
+{{#unless asyncapi.__noSchemas}}
+## Schemas
+{{/unless}}
+
+{{#each asyncapi.components.schemas}}
+ {{~>schema schema=. schemaName=@key~}}
+{{/each}}
diff --git a/templates/markdown/.partials/security.md b/templates/markdown/.partials/security.md
new file mode 100644
index 000000000..8863fff71
--- /dev/null
+++ b/templates/markdown/.partials/security.md
@@ -0,0 +1,30 @@
+{{#if asyncapi._security}}
+
+## Security
+
+
+
+
+ Type |
+ In |
+ Name |
+ Scheme |
+ Format |
+ Description |
+
+
+
+ {{#each asyncapi._security as |security|}}
+
+ {{security.type}} |
+ {{security.in}} |
+ {{security.name}} |
+ {{security.scheme}} |
+ {{security.bearerFormat}} |
+ {{{security.descriptionAsHTML}}} |
+
+ {{/each}}
+
+
+
+{{/if}}
diff --git a/templates/markdown/.partials/tags.md b/templates/markdown/.partials/tags.md
new file mode 100644
index 000000000..4b9b878a2
--- /dev/null
+++ b/templates/markdown/.partials/tags.md
@@ -0,0 +1,7 @@
+
diff --git a/templates/markdown/.partials/topic.md b/templates/markdown/.partials/topic.md
new file mode 100644
index 000000000..cc1d0b9a2
--- /dev/null
+++ b/templates/markdown/.partials/topic.md
@@ -0,0 +1,38 @@
+
+
+{{~#if topic.deprecated}}Deprecated{{~/if}}
+{{~#if topic.publish}}publish
{{~/if}}
+{{~#if topic.subscribe}}subscribe
{{~/if}}
+{{topicName}}
+
+
+{{#if topic.parameters}}
+{{~> parameters params=topic.parameters topicName=topicName ~}}
+{{/if}}
+
+#### Message
+
+{{#if topic.publish.oneOf}}
+You can send one of the following messages:
+{{/if}}
+{{#if topic.subscribe.oneOf}}
+You can send one of the following messages:
+{{/if}}
+
+{{~#each topic.publish.oneOf as |op index| ~}}
+ ##### Message #{{inc index}}
+ {{~> operation operation=op cssClasses='operation--indented'~}}
+{{else}}
+ {{~#if topic.publish ~}}
+ {{~> operation operation=topic.publish~}}
+ {{~/if~}}
+{{~/each~}}
+
+{{~#each topic.subscribe.oneOf as |op index| ~}}
+ ##### Message #{{inc index}}
+ {{~> operation operation=op cssClasses='operation--indented'~}}
+{{else}}
+ {{~#if topic.subscribe ~}}
+ {{~> operation operation=topic.subscribe~}}
+ {{~/if~}}
+{{~/each~}}
diff --git a/templates/markdown/.partials/topics.md b/templates/markdown/.partials/topics.md
new file mode 100644
index 000000000..4566b454d
--- /dev/null
+++ b/templates/markdown/.partials/topics.md
@@ -0,0 +1,5 @@
+## Topics
+
+{{#each asyncapi.topics}}
+{{~>topic topic=. topicName=@key ~}}
+{{/each}}
diff --git a/templates/markdown/index.md b/templates/markdown/index.md
new file mode 100644
index 000000000..a419cef53
--- /dev/null
+++ b/templates/markdown/index.md
@@ -0,0 +1,33 @@
+# {{asyncapi.info.title}} {{asyncapi.info.version}} documentation
+
+{{#if asyncapi.info.x-logo}}
+
+{{/if}}
+
+{{{asyncapi.info.description}}}
+
+## Table of Contents
+
+{{#if asyncapi.info.termsOfService}}
+* [Terms of Service](#termsOfService)
+{{/if}}
+{{#if asyncapi.servers}}
+* [Connection Details](#servers)
+{{/if}}
+{{#if asyncapi.topics}}
+* [Topics](#topics)
+{{/if}}
+{{#if asyncapi.events}}
+* [Events](#events)
+{{/if}}
+{{#if asyncapi.stream}}
+* [Stream](#stream)
+{{/if}}
+{{#unless asyncapi.__noMessages}}
+* [Messages](#messages)
+{{/unless}}
+{{#unless asyncapi.__noSchemas}}
+* [Schemas](#schemas)
+{{/unless}}
+
+{{> content}}
diff --git a/test/docs/allof.yml b/test/docs/allof.yml
new file mode 100644
index 000000000..ecfbc79f8
--- /dev/null
+++ b/test/docs/allof.yml
@@ -0,0 +1,53 @@
+asyncapi: '1.1.0'
+info:
+ title: AllOf example
+ version: '1.0.0'
+
+topics:
+ test:
+ publish:
+ $ref: '#/components/messages/testMessages'
+ test2:
+ subscribe:
+ $ref: '#/components/messages/testMessages2'
+
+components:
+ messages:
+ testMessages:
+ payload:
+ allOf: # allOf in payload schema
+ - $ref: "#/components/schemas/objectWithKey"
+ - $ref: "#/components/schemas/objectWithKey2"
+ testMessages2:
+ payload:
+ $ref: "#/components/schemas/objects"
+
+ schemas:
+ objects:
+ allOf:
+ - type: object
+ properties:
+ key3:
+ allOf:
+ - type: object
+ properties:
+ key31:
+ type: string
+ - type: object
+ properties:
+ key32:
+ type: string
+ - type: object
+ properties:
+ key4:
+ type: string
+ objectWithKey:
+ type: object
+ properties:
+ key:
+ type: string
+ objectWithKey2:
+ type: object
+ properties:
+ key2:
+ type: string
diff --git a/test/docs/anyof.yml b/test/docs/anyof.yml
new file mode 100644
index 000000000..9d07c71e6
--- /dev/null
+++ b/test/docs/anyof.yml
@@ -0,0 +1,39 @@
+asyncapi: '1.1.0'
+info:
+ title: AnyOf example
+ version: '1.0.0'
+
+topics:
+ test:
+ publish:
+ $ref: '#/components/messages/testMessages'
+
+components:
+ messages:
+ testMessages:
+ headers:
+ type: object
+ properties:
+ header:
+ type: string
+ payload:
+ anyOf:
+ - $ref: "#/components/schemas/objectWithKey"
+ - $ref: "#/components/schemas/objectWithKey2"
+
+ schemas:
+ objectWithKey:
+ oneOf:
+ - type: object
+ properties:
+ key31:
+ type: string
+ - type: object
+ properties:
+ key32:
+ type: string
+ objectWithKey2:
+ type: object
+ properties:
+ key2:
+ type: string
diff --git a/test/docs/oneof.yml b/test/docs/oneof.yml
new file mode 100644
index 000000000..ec1337d69
--- /dev/null
+++ b/test/docs/oneof.yml
@@ -0,0 +1,62 @@
+asyncapi: '1.1.0'
+info:
+ title: OneOf example
+ version: '1.0.0'
+
+topics:
+ test:
+ publish:
+ $ref: '#/components/messages/testMessages'
+
+ test2:
+ subscribe:
+ # Use oneOf here if different messages are published on test2 topic.
+ oneOf:
+ - $ref: "#/components/messages/testMessage1"
+ - $ref: "#/components/messages/testMessage2"
+
+components:
+ messages:
+ testMessages:
+ headers:
+ type: object
+ properties:
+ header:
+ type: string
+ payload:
+ oneOf: # oneOf in payload schema
+ - $ref: "#/components/schemas/objectWithKey"
+ - $ref: "#/components/schemas/objectWithKey2"
+ testMessage1:
+ headers:
+ type: object
+ properties:
+ header1:
+ type: string
+ payload:
+ $ref: "#/components/schemas/objectWithKey"
+ testMessage2:
+ headers:
+ type: object
+ properties:
+ header2:
+ type: string
+ payload:
+ $ref: "#/components/schemas/objectWithKey2"
+
+ schemas:
+ objectWithKey:
+ oneOf:
+ - type: object
+ properties:
+ key31:
+ type: string
+ - type: object
+ properties:
+ key32:
+ type: string
+ objectWithKey2:
+ type: object
+ properties:
+ key2:
+ type: string
diff --git a/test/docs/sample.yml b/test/docs/sample.yml
new file mode 100644
index 000000000..3b1bc0174
--- /dev/null
+++ b/test/docs/sample.yml
@@ -0,0 +1,110 @@
+asyncapi: '1.1.0'
+info:
+ title: Streetlights API
+ version: '1.0.0'
+ 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 ๐
+ license:
+ name: Apache 2.0
+ url: https://www.apache.org/licenses/LICENSE-2.0
+baseTopic: smartylighting.streetlights.1.0
+
+servers:
+ - url: api.streetlights.smartylighting.com:{port}
+ scheme: mqtt
+ description: Test broker
+ variables:
+ port:
+ description: Secure connection (TLS) is available through port 8883.
+ default: '1883'
+ enum:
+ - '1883'
+ - '8883'
+
+security:
+ - apiKey: []
+
+topics:
+ event.{streetlightId}.lighting.measured:
+ parameters:
+ - name: streetlightId
+ description: The ID of the streetlight.
+ schema:
+ type: string
+ publish:
+ $ref: '#/components/messages/lightMeasured'
+
+ action.{streetlightId}.turn.on:
+ subscribe:
+ $ref: '#/components/messages/turnOnOff'
+
+ action.{streetlightId}.turn.off:
+ subscribe:
+ $ref: '#/components/messages/turnOnOff'
+
+ action.{streetlightId}.dim:
+ subscribe:
+ $ref: '#/components/messages/dimLight'
+
+components:
+ messages:
+ lightMeasured:
+ summary: Inform about environmental lighting conditions for a particular streetlight.
+ payload:
+ $ref: "#/components/schemas/lightMeasuredPayload"
+ turnOnOff:
+ summary: Command a particular streetlight to turn the lights on or off.
+ payload:
+ $ref: "#/components/schemas/turnOnOffPayload"
+ dimLight:
+ summary: Command a particular streetlight to dim the lights.
+ payload:
+ $ref: "#/components/schemas/dimLightPayload"
+
+ schemas:
+ lightMeasuredPayload:
+ type: object
+ properties:
+ lumens:
+ type: integer
+ minimum: 0
+ description: Light intensity measured in lumens.
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ turnOnOffPayload:
+ type: object
+ properties:
+ command:
+ type: string
+ enum:
+ - on
+ - off
+ description: Whether to turn on or off the light.
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ dimLightPayload:
+ type: object
+ properties:
+ percentage:
+ type: integer
+ description: Percentage to which the light should be dimmed to.
+ minimum: 0
+ maximum: 100
+ sentAt:
+ $ref: "#/components/schemas/sentAt"
+ sentAt:
+ type: string
+ format: date-time
+ description: Date and time when the message was sent.
+
+ securitySchemes:
+ apiKey:
+ type: apiKey
+ in: user
+ description: Provide your API key as the user and leave the password empty.
diff --git a/test/docs/sample_100.yml b/test/docs/sample_100.yml
new file mode 100644
index 000000000..43d859aa5
--- /dev/null
+++ b/test/docs/sample_100.yml
@@ -0,0 +1,161 @@
+asyncapi: "1.0.0"
+info:
+ title: AsyncAPI Sample
+ version: "1.0.0"
+ x-logo: https://avatars0.githubusercontent.com/u/16401334?v=4&s=200
+ description: |
+ This is a simple example of an _AsyncAPI_ document.
+ termsOfService: https://api.company.com/terms
+baseTopic: 'hitch'
+
+servers:
+ - url: api.company.com:{port}/{app-id}
+ description: Allows you to connect using the MQTT protocol.
+ scheme: mqtt
+ variables:
+ app-id:
+ default: demo
+ description: You can find your `app-id` in our control panel, under the auth tab.
+ port:
+ enum:
+ - '5676'
+ - '5677'
+ default: '5676'
+ - url: api.company.com:{port}/{app-id}
+ description: Allows you to connect using the AMQP protocol.
+ scheme: amqp
+ variables:
+ app-id:
+ default: demo
+ description: You can find your `app-id` in our control panel, under the auth tab.
+ port:
+ enum:
+ - '5676'
+ - '5677'
+ default: '5676'
+
+topics:
+ accounts.1.0.action.user.signup:
+ publish:
+ $ref: "#/components/messages/userSignUp"
+ accounts.1.0.event.user.signup:
+ subscribe:
+ $ref: "#/components/messages/userSignedUp"
+
+components:
+ messages:
+ userSignUp:
+ deprecated: true
+ summary: Action to sign a user up.
+ description: |
+ Multiline description of what this action does. **It allows Markdown.**
+ tags:
+ - name: user
+ - name: signup
+ headers:
+ type: object
+ properties:
+ qos:
+ $ref: "#/components/schemas/MQTTQoSHeader"
+ retainFlag:
+ $ref: "#/components/schemas/MQTTRetainHeader"
+ payload:
+ type: object
+ properties:
+ user:
+ $ref: "#/components/schemas/userCreate"
+ signup:
+ $ref: "#/components/schemas/signup"
+
+
+ userSignedUp:
+ payload:
+ type: object
+ properties:
+ test:
+ type: array
+ items:
+ type: object
+ properties:
+ key1:
+ type: string
+ key2:
+ type: integer
+ user:
+ $ref: "#/components/schemas/user"
+ signup:
+ $ref: "#/components/schemas/signup"
+ schemas:
+ id:
+ title: id
+ description: Resource identifier
+ type: string
+ username:
+ title: username
+ description: User handle
+ type: string
+ datetime:
+ title: datetime
+ description: Date and Time of the message
+ type: string
+ format: date-time
+ MQTTQoSHeader:
+ title: qos
+ description: Quality of Service
+ type: integer
+ format: int32
+ default: 1
+ enum:
+ - 0
+ - 2
+ MQTTRetainHeader:
+ title: retainFlag
+ description: |
+ This flag determines if the message will be saved by the broker for the specified
+ topic as last known good value. New clients that subscribe to that topic will receive
+ the last retained message on that topic instantly after subscribing. More on retained messages
+ and best practices in one of the next posts.
+ type: boolean
+ default: false
+ user:
+ type: object
+ required:
+ - id
+ - username
+ properties:
+ id:
+ description: User Id
+ $ref: "#/components/schemas/id"
+ full_name:
+ description: User full name
+ type: string
+ username:
+ $ref: "#/components/schemas/username"
+ userCreate:
+ type: object
+ required:
+ - username
+ properties:
+ full_name:
+ description: User full name
+ type: string
+ username:
+ $ref: "#/components/schemas/username"
+
+ signup:
+ type: object
+ required:
+ - method
+ - datetime
+ properties:
+ method:
+ description: Signup method
+ type: string
+ enum:
+ - email
+ - facebook
+ - twitter
+ - github
+ - google
+ datetime:
+ $ref: "#/components/schemas/datetime"