Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable string input for generate #1039

Merged
merged 21 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved

/** @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