Skip to content

Commit

Permalink
feat: enable string input for generate (#1039)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Sep 28, 2023
1 parent cccd9fa commit 553b5e2
Show file tree
Hide file tree
Showing 8 changed files with 1,925 additions and 665 deletions.
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#we need to explicitly exclude them as some are commit to the repo
sonar.exclusions=test/test-project/node_modules/@asyncapi/html-template/**/*
sonar.exclusions=test/**/*
71 changes: 45 additions & 26 deletions lib/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,19 @@ class Generator {
* console.error(e);
* }
*
* @param {AsyncAPIDocument} asyncapiDocument AsyncAPIDocument object to use as source.
* @param {AsyncAPIDocument | string} asyncapiDocument AsyncAPIDocument object to use as source.
* @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information. Remember to use the right options to the right parser depending on the template you are using.
* @return {Promise}
*/
async generate(asyncapiDocument) {
if (!isAsyncAPIDocument(asyncapiDocument)) throw new Error('Parameter "asyncapiDocument" must be an AsyncAPIDocument object.');
// eslint-disable-next-line sonarjs/cognitive-complexity
async generate(asyncapiDocument, parseOptions = {}) {
const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument);
const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string';
if (!isAlreadyParsedDocument && !isParsableCompatible) {
throw new Error('Parameter "asyncapiDocument" must be a non-empty string or an already parsed AsyncAPI document.');
}
this.asyncapi = this.originalAsyncAPI = asyncapiDocument;

this.asyncapi = asyncapiDocument;
if (this.output === 'fs') {
xfs.mkdirpSync(this.targetDir);
if (!this.forceWrite) await this.verifyTargetDir(this.targetDir);
Expand All @@ -176,11 +182,11 @@ class Generator {
this.templateName = templatePkgName;
this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME);
await this.loadTemplateConfig();
validateTemplateConfig(this.templateConfig, this.templateParams, asyncapiDocument);
await this.configureTemplate();

// use the expected document API based on `templateConfig.apiVersion` value
this.asyncapi = asyncapiDocument = getProperApiDocument(asyncapiDocument, this.templateConfig);
await this.parseInput(this.asyncapi, parseOptions);

validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi);
await this.configureTemplate();

if (!isReactTemplate(this.templateConfig)) {
await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME);
Expand All @@ -192,17 +198,38 @@ class Generator {
const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint);
if (!(await exists(entrypointPath))) throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`);
if (this.output === 'fs') {
await this.generateFile(asyncapiDocument, path.basename(entrypointPath), path.dirname(entrypointPath));
await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath));
await this.launchHook('generate:after');
} else if (this.output === 'string') {
return this.renderFile(asyncapiDocument, entrypointPath);
return this.renderFile(this.asyncapi, entrypointPath);
}
} else {
await this.generateDirectoryStructure(asyncapiDocument);
await this.generateDirectoryStructure(this.asyncapi);
await this.launchHook('generate:after');
}
}

/**
* Parse the generator input based on the template `templateConfig.apiVersion` value.
*/
async parseInput(asyncapiDocument, parseOptions = {}) {
const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument);
// use the expected document API based on `templateConfig.apiVersion` value
if (isAlreadyParsedDocument) {
this.asyncapi = getProperApiDocument(asyncapiDocument, this.templateConfig);
} else {
/** @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 { document, diagnostics } = await parse(asyncapiDocument, parseOptions, this);
if (!document) {
const err = new Error('Input is not a correct AsyncAPI document so it cannot be processed.');
err.diagnostics = diagnostics;
throw err;
} else {
this.asyncapi = document;
}
}
}

/**
* Configure the templates based the desired renderer.
*/
Expand Down Expand Up @@ -250,22 +277,15 @@ class Generator {
*
* @param {String} asyncapiString AsyncAPI string to use as source.
* @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
* @deprecated Use the `generate` function instead. Just change the function name and it works out of the box.
* @return {Promise}
*/
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 { document, diagnostics } = await parse(asyncapiString, parseOptions, this);
if (!document) {
const err = new Error('Input is not a correct AsyncAPI document so it cannot be processed.');
err.diagnostics = diagnostics;
throw err;
const isParsableCompatible = asyncapiString && typeof asyncapiString === 'string';
if (!isParsableCompatible) {
throw new Error('Parameter "asyncapiString" must be a non-empty string.');
}
return this.generate(document);
return this.generate(asyncapiString, parseOptions);
}

/**
Expand All @@ -292,7 +312,7 @@ class Generator {
*/
async generateFromURL(asyncapiURL) {
const doc = await fetchSpec(asyncapiURL);
return this.generateFromString(doc, { path: asyncapiURL });
return this.generate(doc, { path: asyncapiURL });
}

/**
Expand All @@ -319,7 +339,7 @@ class Generator {
*/
async generateFromFile(asyncapiFile) {
const doc = await readFile(asyncapiFile, { encoding: 'utf8' });
return this.generateFromString(doc, { path: asyncapiFile });
return this.generate(doc, { path: asyncapiFile });
}

/**
Expand Down Expand Up @@ -416,7 +436,6 @@ class Generator {
*/
getAllParameters(asyncapiDocument) {
const parameters = new Map();

if (usesNewAPI(this.templateConfig)) {
asyncapiDocument.channels().all().forEach(channel => {
channel.parameters().all().forEach(parameter => {
Expand Down
13 changes: 9 additions & 4 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ const { ConvertDocumentParserAPIVersion, NewParser } = require('@smoya/multi-par
const parser = module.exports;

/**
* Conver the template defined value `apiVersion: 'v1'` to only contain the numeric value `1`.
* Convert the template defined value `apiVersion: 'v1'` to only contain the numeric value `1`.
*/
parser.sanitizeTemplateApiVersion = (apiVersion) => {
if (apiVersion && apiVersion.length > 1) {
return apiVersion.substring('1');
return apiVersion.substring(1);
}
return apiVersion;
};

parser.parse = (asyncapi, oldOptions, generator) => {
parser.parse = async (asyncapi, oldOptions, generator) => {
let apiVersion = this.sanitizeTemplateApiVersion(generator.templateConfig.apiVersion);
// Defaulting to apiVersion v1 to convert it to the Parser-API v1 afterwards.
if (!this.usesNewAPI(generator.templateConfig)) {
apiVersion = '1';
}
const options = convertOldOptionsToNew(oldOptions, generator);
const parser = NewParser(apiVersion, {parserOptions: options, includeSchemaParsers: true});
return parser.parse(asyncapi, options);
const { document, diagnostics } = await parser.parse(asyncapi, options);
if (!document) {
return {document, diagnostics};
}
const correctDocument = this.getProperApiDocument(document, generator.templateConfig);
return {document: correctDocument, diagnostics};
};

/**
Expand Down
30 changes: 19 additions & 11 deletions lib/templateConfigValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const semver = require('semver');
const Ajv = require('ajv');
const { getGeneratorVersion } = require('./utils');
const levenshtein = require('levenshtein-edit-distance');
// eslint-disable-next-line no-unused-vars
const {AsyncAPIDocumentInterface, AsyncAPIDocument} = require('@asyncapi/parser');
const { usesNewAPI } = require('./parser');

const ajv = new Ajv({ allErrors: true });

Expand All @@ -12,24 +15,29 @@ const supportedParserAPIMajorVersions = [
];

/**
* Validates the template configuration.
*
* @param {Object} templateConfig Template configuration.
* @param {Object} templateParams Params specified when running generator.
* @param {AsyncAPIDocument} asyncapiDocument AsyncAPIDocument object to use as source.
* @return {Boolean}
*/
* Validates the template configuration.
*
* @param {Object} templateConfig Template configuration.
* @param {Object} templateParams Params specified when running generator.
* @param {AsyncAPIDocumentInterface | AsyncAPIDocument} asyncapiDocument AsyncAPIDocument object to use as source.
* @return {Boolean}
*/
module.exports.validateTemplateConfig = (templateConfig, templateParams, asyncapiDocument) => {
const { parameters, supportedProtocols, conditionalFiles, generator, apiVersion } = templateConfig;

validateConditionalFiles(conditionalFiles);
isTemplateCompatible(generator, apiVersion);
isRequiredParamProvided(parameters, templateParams);
isProvidedTemplateRendererSupported(templateConfig);
if (asyncapiDocument) {
const server = asyncapiDocument.servers().get(templateParams.server);
isServerProvidedInDocument(server, templateParams.server);
if (asyncapiDocument && templateParams.server) {
let server;
if (usesNewAPI(templateConfig)) {
server = asyncapiDocument.servers().get(templateParams.server);
} else {
server = asyncapiDocument.servers()[templateParams.server];
}
isServerProtocolSupported(server, supportedProtocols, templateParams.server);
isServerProvidedInDocument(server, templateParams.server);
}

isProvidedParameterSupported(parameters, templateParams);
Expand All @@ -49,7 +57,7 @@ function isTemplateCompatible(generator, apiVersion) {
}

if (typeof apiVersion === 'string' && !supportedParserAPIMajorVersions.includes(apiVersion)) {
throw new Error(`The version specified in apiVersion is not supported by this Generator version. Supported versions are: ${supportedParserAPIMajorVersions.toString()}`);
throw new Error(`The version specified in apiVersion is not supported by this Generator version. Supported versions are: ${supportedParserAPIMajorVersions.join(', ')}`);
}
}

Expand Down
Loading

0 comments on commit 553b5e2

Please sign in to comment.