Skip to content

Commit

Permalink
feat: upgrade @asyncapi/parser to v2.0.0 and use the new parser-api (
Browse files Browse the repository at this point in the history
…#960)

Co-authored-by: Jonas Lagoni <[email protected]>%0ACo-authored-by: Matatjahu <[email protected]>
  • Loading branch information
smoya and magicmatatjahu authored May 18, 2023
1 parent 0cf5b9a commit 4b092d0
Show file tree
Hide file tree
Showing 15 changed files with 17,034 additions and 1,609 deletions.
9 changes: 6 additions & 3 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const path = require('path');
const os = require('os');
const program = require('commander');
const xfs = require('fs.extra');
const { DiagnosticSeverity } = require('@asyncapi/parser/cjs');
const packageInfo = require('./package.json');
const Generator = require('./lib/generator');
const Watcher = require('./lib/watcher');
Expand Down Expand Up @@ -64,8 +65,10 @@ const mapBaseUrlParser = v => {
const showError = err => {
console.error(red('Something went wrong:'));
console.error(red(err.stack || err.message));
if (err.errors) console.error(red(JSON.stringify(err.errors)));
if (err.validationErrors) console.error(red(JSON.stringify(err.validationErrors, null, 4)));
if (err.diagnostics) {
const errorDiagnostics = err.diagnostics.filter(diagnostic => diagnostic.severity === DiagnosticSeverity.Error);
console.error(red(`Errors:\n${JSON.stringify(errorDiagnostics, undefined, 2)}`));
}
};
const showErrorAndExit = err => {
showError(err);
Expand Down Expand Up @@ -127,7 +130,7 @@ xfs.mkdirp(program.output, async err => {
console.warn(`WARNING: ${template} is a remote template. Changes may be lost on subsequent installations.`);
}

watcher.watch(watcherHandler, (paths) => {
await watcher.watch(watcherHandler, (paths) => {
showErrorAndExit({ message: `[WATCHER] Could not find the file path ${paths}, are you sure it still exists? If it has been deleted or moved please rerun the generator.` });
});
}
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The `generator` property from `package.json` file must contain a JSON object tha
|Name|Type|Description|
|---|---|---|
|`renderer`| String | Its value can be either `react` or `nunjucks` (default).
|`apiVersion`| String | Determines which **major** version of the [Parser-API](https://github.com/asyncapi/parser-api) the template uses. For example, `v1` for `v1.x.x`. If not specified, the Generator assumes the template is not compatible with the Parser-API so it will use the [Parser-JS v1 API](https://github.com/asyncapi/parser-js/tree/v1.18.1#api-documentation). If the template uses a version of the Parser-API that is not supported by the Generator, the Generator will throw an error.
|`supportedProtocols`| [String] | A list with all the protocols this template supports.
|`parameters`| Object[String, Object] | An object with all the parameters that can be passed when generating the template. When using the command line, it's done by indicating `--param name=value` or `-p name=value`.
|`parameters[param].description`| String | A user-friendly description about the parameter.
Expand All @@ -27,6 +28,7 @@ The `generator` property from `package.json` file must contain a JSON object tha
"generator":
{
"renderer": "react",
"apiVersion": "v1",
"supportedProtocols": ["amqp", "mqtt"],
"parameters": {
"server": {
Expand Down
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module.exports = {
clearMocks: true,
moduleNameMapper: {
'^nimma/legacy$': '<rootDir>/node_modules/nimma/dist/legacy/cjs/index.js',
'^nimma/(.*)': '<rootDir>/node_modules/nimma/dist/cjs/$1',
},
};
123 changes: 69 additions & 54 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@ const path = require('path');
const fs = require('fs');
const xfs = require('fs.extra');
const minimatch = require('minimatch');
const parser = require('@asyncapi/parser');
const { configureReact, renderReact, saveRenderedReactContent } = require('./renderer/react');
const { configureNunjucks, renderNunjucks } = require('./renderer/nunjucks');
const { parse, AsyncAPIDocument } = parser;
const ramlDtParser = require('@asyncapi/raml-dt-schema-parser');
const openapiSchemaParser = require('@asyncapi/openapi-schema-parser');
const avroSchemaParser = require('@asyncapi/avro-schema-parser');
const jmespath = require('jmespath');
const filenamify = require('filenamify');
const git = require('simple-git');
const log = require('loglevel');
const Arborist = require('@npmcli/arborist');
const { isAsyncAPIDocument } = require('@asyncapi/parser/cjs/document');

const { configureReact, renderReact, saveRenderedReactContent } = require('./renderer/react');
const { configureNunjucks, renderNunjucks } = require('./renderer/nunjucks');
const { validateTemplateConfig } = require('./templateConfigValidator');
const {
convertMapToObject,
Expand All @@ -30,8 +27,9 @@ const {
registerSourceMap,
registerTypeScript,
getTemplateDetails,
getMapBaseUrlToFolderResolver
convertCollectionToObject,
} = require('./utils');
const { parse, usesNewAPI, getProperApiDocument } = require('./parser');
const { registerFilters } = require('./filtersRegistry');
const { registerHooks } = require('./hooksRegistry');

Expand All @@ -57,9 +55,6 @@ const shouldIgnoreDir = dirPath =>
dirPath === '.git'
|| dirPath.startsWith(`.git${path.sep}`);

parser.registerSchemaParser(openapiSchemaParser);
parser.registerSchemaParser(ramlDtParser);
parser.registerSchemaParser(avroSchemaParser);
registerSourceMap();
registerTypeScript();

Expand Down Expand Up @@ -164,10 +159,9 @@ class Generator {
* @return {Promise}
*/
async generate(asyncapiDocument) {
if (!(asyncapiDocument instanceof AsyncAPIDocument)) throw new Error('Parameter "asyncapiDocument" must be an AsyncAPIDocument object.');
if (!isAsyncAPIDocument(asyncapiDocument)) throw new Error('Parameter "asyncapiDocument" must be an AsyncAPIDocument object.');

this.asyncapi = asyncapiDocument;

if (this.output === 'fs') {
xfs.mkdirpSync(this.targetDir);
if (!this.forceWrite) await this.verifyTargetDir(this.targetDir);
Expand All @@ -185,6 +179,9 @@ class Generator {
validateTemplateConfig(this.templateConfig, this.templateParams, asyncapiDocument);
await this.configureTemplate();

// use new or old document API based on `templateConfig.apiVersion` value
this.asyncapi = asyncapiDocument = getProperApiDocument(asyncapiDocument, this.templateConfig);

if (!isReactTemplate(this.templateConfig)) {
await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME);
}
Expand Down Expand Up @@ -252,18 +249,23 @@ class Generator {
* }
*
* @param {String} asyncapiString AsyncAPI string to use as source.
* @param {Object} [parserOptions={}] AsyncAPI parser options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
* @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
* @return {Promise}
*/
async generateFromString(asyncapiString, parserOptions = {}) {
async generateFromString(asyncapiString, parseOptions = {}) {
if (!asyncapiString || typeof asyncapiString !== 'string') throw new Error('Parameter "asyncapiString" must be a non-empty string.');

/** @type {String} AsyncAPI string to use as a source. */
this.originalAsyncAPI = asyncapiString;

/** @type {AsyncAPIDocument} Parsed AsyncAPI schema. See {@link https://github.com/asyncapi/parser-js/blob/master/API.md#module_@asyncapi/parser+AsyncAPIDocument|AsyncAPIDocument} for details on object structure. */
const asyncapi = await parse(asyncapiString, parserOptions);
return this.generate(asyncapi);
const { document, diagnostics } = await parse(asyncapiString, parseOptions, this);
if (!document) {
const err = new Error('Input is not a corrent AsyncAPI document so it cannot be processed.');
err.diagnostics = diagnostics;
throw err;
}
return this.generate(document);
}

/**
Expand All @@ -290,12 +292,7 @@ class Generator {
*/
async generateFromURL(asyncapiURL) {
const doc = await fetchSpec(asyncapiURL);
const parserOptions = {};
if (this.mapBaseUrlToFolder.url) {
parserOptions.resolve = {resolver: getMapBaseUrlToFolderResolver(this.mapBaseUrlToFolder)};
}

return this.generateFromString(doc, parserOptions);
return this.generateFromString(doc, { path: asyncapiURL });
}

/**
Expand All @@ -322,12 +319,7 @@ class Generator {
*/
async generateFromFile(asyncapiFile) {
const doc = await readFile(asyncapiFile, { encoding: 'utf8' });
const parserOptions = { path: asyncapiFile };
if (this.mapBaseUrlToFolder.url) {
parserOptions.resolve = {resolver: getMapBaseUrlToFolderResolver(this.mapBaseUrlToFolder)};
}

return this.generateFromString(doc, parserOptions);
return this.generateFromString(doc, { path: asyncapiFile });
}

/**
Expand Down Expand Up @@ -425,18 +417,30 @@ class Generator {
getAllParameters(asyncapiDocument) {
const parameters = new Map();

if (asyncapiDocument.hasChannels()) {
asyncapiDocument.channelNames().forEach(channelName => {
const channel = asyncapiDocument.channel(channelName);
for (const [key, value] of Object.entries(channel.parameters())) {
parameters.set(key, value);
}
if (usesNewAPI(this.templateConfig)) {
asyncapiDocument.channels().all().forEach(channel => {
channel.parameters().all().forEach(parameter => {
parameters.set(parameter.id(), parameter);
});
});
}

if (asyncapiDocument.hasComponents()) {
for (const [key, value] of Object.entries(asyncapiDocument.components().parameters())) {
parameters.set(key, value);
asyncapiDocument.components().channelParameters().all().forEach(parameter => {
parameters.set(parameter.id(), parameter);
});
} else {
if (asyncapiDocument.hasChannels()) {
asyncapiDocument.channelNames().forEach(channelName => {
const channel = asyncapiDocument.channel(channelName);
for (const [key, value] of Object.entries(channel.parameters())) {
parameters.set(key, value);
}
});
}

if (asyncapiDocument.hasComponents()) {
for (const [key, value] of Object.entries(asyncapiDocument.components().parameters())) {
parameters.set(key, value);
}
}
}

Expand All @@ -451,9 +455,6 @@ class Generator {
* @return {Promise}
*/
generateDirectoryStructure(asyncapiDocument) {
const objectMap = {};
asyncapiDocument.allSchemas().forEach((schema, schemaId) => { if (schema.type() === 'object') objectMap[schemaId] = schema; });

return new Promise((resolve, reject) => {
xfs.mkdirpSync(this.targetDir);

Expand All @@ -463,7 +464,7 @@ class Generator {

walker.on('file', async (root, stats, next) => {
try {
await this.filesGenerationHandler(asyncapiDocument, objectMap, root, stats, next);
await this.filesGenerationHandler(asyncapiDocument, root, stats, next);
} catch (e) {
reject(e);
}
Expand Down Expand Up @@ -514,16 +515,31 @@ class Generator {
* @param {String} stats Information about the file.
* @param {Function} next Callback function
*/
async filesGenerationHandler(asyncapiDocument, objectMap, root, stats, next) {
const fileNamesForSeparation = {
channel: asyncapiDocument.channels(),
message: convertMapToObject(asyncapiDocument.allMessages()),
securityScheme: asyncapiDocument.components() ? asyncapiDocument.components().securitySchemes() : {},
schema: asyncapiDocument.components() ? asyncapiDocument.components().schemas() : {},
objectSchema: objectMap,
parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
everySchema: convertMapToObject(asyncapiDocument.allSchemas()),
};
async filesGenerationHandler(asyncapiDocument, root, stats, next) {
let fileNamesForSeparation = {};
if (usesNewAPI(this.templateConfig)) {
fileNamesForSeparation = {
channel: convertCollectionToObject(asyncapiDocument.channels().all(), 'address'),
message: convertCollectionToObject(asyncapiDocument.messages().all(), 'id'),
securityScheme: convertCollectionToObject(asyncapiDocument.components().securitySchemes().all(), 'id'),
schema: convertCollectionToObject(asyncapiDocument.components().schemas().all(), 'id'),
objectSchema: convertCollectionToObject(asyncapiDocument.schemas().all().filter(schema => schema.type() === 'object'), 'id'),
parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
everySchema: convertCollectionToObject(asyncapiDocument.schemas().all(), 'id'),
};
} else {
const objectSchema = {};
asyncapiDocument.allSchemas().forEach((schema, schemaId) => { if (schema.type() === 'object') objectSchema[schemaId] = schema; });
fileNamesForSeparation = {
channel: asyncapiDocument.channels(),
message: convertMapToObject(asyncapiDocument.allMessages()),
securityScheme: asyncapiDocument.components() ? asyncapiDocument.components().securitySchemes() : {},
schema: asyncapiDocument.components() ? asyncapiDocument.components().schemas() : {},
objectSchema,
parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
everySchema: convertMapToObject(asyncapiDocument.allSchemas()),
};
}

// Check if the filename dictates it should be separated
let wasSeparated = false;
Expand Down Expand Up @@ -663,7 +679,6 @@ class Generator {
}

if (this.isNonRenderableFile(relativeSourceFile)) return await copyFile(sourceFile, targetFile);

await this.renderAndWriteToFile(asyncapiDocument, sourceFile, targetFile);
}

Expand Down
Loading

0 comments on commit 4b092d0

Please sign in to comment.