Skip to content

Commit

Permalink
fix: add v3 checks for all commands (#697)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Jul 27, 2023
1 parent 655a03d commit 94790a8
Show file tree
Hide file tree
Showing 16 changed files with 640 additions and 173 deletions.
354 changes: 201 additions & 153 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"strip-ansi": "^6.0.0",
"unzipper": "^0.10.11",
"wrap-ansi": "^4.0.0",
"ws": "^8.2.3"
"ws": "^8.2.3",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@asyncapi/minimaltemplate": "./test/minimaltemplate",
Expand All @@ -61,6 +62,7 @@
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
"@typescript-eslint/parser": "^5.38.1",
"@types/js-yaml": "^4.0.5",
"acorn": "^8.5.0",
"chai": "^4.3.6",
"cross-env": "^7.0.3",
Expand Down
18 changes: 14 additions & 4 deletions src/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Command from '../base';
import bundle from '@asyncapi/bundler';
import { promises } from 'fs';
import path from 'path';
import { load } from '../models/SpecificationFile';
import { Specification, load } from '../models/SpecificationFile';

const { writeFile } = promises;

Expand Down Expand Up @@ -32,9 +32,19 @@ export default class Bundle extends Command {
let baseFile;
const outputFormat = path.extname(argv[0]);
const AsyncAPIFiles = await this.loadFiles(argv);

const containsAsyncAPI3 = AsyncAPIFiles.filter((file) => {
return file.isAsyncAPI3();
});
if (containsAsyncAPI3.length > 0) {
this.error('One of the files you tried to bundle is AsyncAPI v3 format, the bundle command does not support it yet, please checkout https://github.com/asyncapi/bundler/issues/133');
}

if (flags.base) {baseFile = (await load(flags.base)).text();}

const document = await bundle(AsyncAPIFiles,
const fileContents = AsyncAPIFiles.map((file) => file.text());

const document = await bundle(fileContents,
{
referenceIntoComponents: flags['reference-into-components'],
base: baseFile
Expand Down Expand Up @@ -65,11 +75,11 @@ export default class Bundle extends Command {
}
}

async loadFiles(filepaths: string[]): Promise<string[]> {
async loadFiles(filepaths: string[]): Promise<Specification[]> {
const files = [];
for (const filepath of filepaths) {
const file = await load(filepath);
files.push(file.text());
files.push(file);
}
return files;
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default class Convert extends Command {
this.log(`URL ${specFile.getFileURL()} successfully converted!`);
}
}

if (typeof convertedFile === 'object') {
convertedFileFormatted = JSON.stringify(convertedFile, null, 4);
} else {
Expand Down
13 changes: 11 additions & 2 deletions src/commands/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export default class Diff extends Command {

try {
firstDocument = await load(firstDocumentPath);

if (firstDocument.isAsyncAPI3()) {
this.error('Diff command does not support AsyncAPI v3 yet which was your first document, please checkout https://github.com/asyncapi/diff/issues/154');
}

enableWatch(watchMode, {
spec: firstDocument,
handler: this,
Expand All @@ -107,6 +112,11 @@ export default class Diff extends Command {

try {
secondDocument = await load(secondDocumentPath);

if (secondDocument.isAsyncAPI3()) {
this.error('Diff command does not support AsyncAPI v3 yet which was your second document, please checkout https://github.com/asyncapi/diff/issues/154');
}

enableWatch(watchMode, {
spec: secondDocument,
handler: this,
Expand Down Expand Up @@ -265,8 +275,7 @@ function throwOnBreakingChange(diffOutput: AsyncAPIDiff, outputFormat: string) {
const breakingChanges = diffOutput.breaking();
if (
(outputFormat === 'json' && breakingChanges.length !== 0) ||
((outputFormat === 'yaml' || outputFormat === 'yml') && breakingChanges !== '[]\n') ||
(outputFormat === 'md' && breakingChanges.length !== 0)
((outputFormat === 'yaml' || outputFormat === 'yml') && breakingChanges !== '[]\n')
) {
throw new DiffBreakingChangeError();
}
Expand Down
35 changes: 34 additions & 1 deletion src/commands/generate/fromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,32 @@ interface ParsedFlags {
mapBaseUrlToFolder: IMapBaseUrlToFlag
}

const templatesNotSupportingV3: Record<string, string> = {
'@asyncapi/minimaltemplate': 'some link', // For testing purpose
'@asyncapi/html-template': 'https://github.com/asyncapi/html-template/issues/430',
'@asyncapi/dotnet-nats-template': 'https://github.com/asyncapi/dotnet-nats-template/issues/384',
'@asyncapi/ts-nats-template': 'https://github.com/asyncapi/ts-nats-template/issues/545',
'@asyncapi/python-paho-template': 'https://github.com/asyncapi/python-paho-template/issues/189',
'@asyncapi/nodejs-ws-template': 'https://github.com/asyncapi/nodejs-ws-template/issues/294',
'@asyncapi/java-spring-cloud-stream-template': 'https://github.com/asyncapi/java-spring-cloud-stream-template/issues/336',
'@asyncapi/go-watermill-template': 'https://github.com/asyncapi/go-watermill-template/issues/243',
'@asyncapi/java-spring-template': 'https://github.com/asyncapi/java-spring-template/issues/308',
'@asyncapi/markdown-template': 'https://github.com/asyncapi/markdown-template/issues/341',
'@asyncapi/nodejs-template': 'https://github.com/asyncapi/nodejs-template/issues/215',
'@asyncapi/java-template': 'https://github.com/asyncapi/java-template/issues/118',
'@asyncapi/php-template': 'https://github.com/asyncapi/php-template/issues/191'
};

/**
* Verify that a given template support v3, if not, return the link to the issue that needs to be solved.
*/
function verifyTemplateSupportForV3(template: string) {
if (templatesNotSupportingV3[`${template}`] !== undefined) {
return templatesNotSupportingV3[`${template}`];
}
return undefined;
}

export default class Template extends Command {
static description = 'Generates whatever you want using templates compatible with AsyncAPI Generator.';

Expand Down Expand Up @@ -99,14 +125,21 @@ export default class Template extends Command {
mapBaseUrlToFolder: parsedFlags.mapBaseUrlToFolder,
disabledHooks: parsedFlags.disableHooks,
};
const asyncapiInput = (await load(asyncapi)) || (await load());

const watchTemplate = flags['watch'];
const genOption: any = {};
if (flags['map-base-url']) {
genOption.resolve = {resolve: this.getMapBaseUrlToFolderResolver(parsedFlags.mapBaseUrlToFolder)};
}

await this.generate(asyncapi, template, output, options, genOption);
if (asyncapiInput.isAsyncAPI3()) {
const v3IssueLink = verifyTemplateSupportForV3(template);
if (v3IssueLink !== undefined) {
this.error(`${template} template does not support AsyncAPI v3 documents, please checkout ${v3IssueLink}`);
}
}
await this.generate(asyncapi, template, output, options, genOption);
if (watchTemplate) {
const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption);
await this.runWatchMode(asyncapi, template, output, watcherHandler);
Expand Down
3 changes: 3 additions & 0 deletions src/commands/generate/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ export default class Models extends Command {
const { tsModelType, tsEnumType, tsIncludeComments, tsModuleSystem, tsExportType, tsJsonBinPack, tsMarshalling, tsExampleInstance, namespace, csharpAutoImplement, csharpArrayType, csharpNewtonsoft, csharpHashcode, csharpEqual, csharpSystemJson, packageName, output } = flags;
const { language, file } = args;
const inputFile = (await load(file)) || (await load());
if (inputFile.isAsyncAPI3()) {
this.error('Generate Models command does not support AsyncAPI v3 yet, please checkout https://github.com/asyncapi/modelina/issues/1376');
}
const { document, diagnostics ,status } = await parse(this, inputFile, flags);
if (!document || status === 'invalid') {
const severityErrors = diagnostics.filter((obj) => obj.severity === 0);
Expand Down
28 changes: 21 additions & 7 deletions src/commands/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class Optimize extends Command {
'asyncapi optimize ./asyncapi.yaml --optimization=remove-components,reuse-components,move-to-components --no-tty',
'asyncapi optimize ./asyncapi.yaml --optimization=remove-components,reuse-components,move-to-components --output=terminal --no-tty',
];

static flags = {
help: Flags.help({ char: 'h' }),
optimization: Flags.string({char: 'p', default: Object.values(Optimizations), options: Object.values(Optimizations), multiple: true, description: 'select the type of optimizations that you want to apply.'}),
Expand All @@ -48,10 +48,24 @@ export default class Optimize extends Command {
const { args, flags } = await this.parse(Optimize); //NOSONAR
const filePath = args['spec-file'];
let specFile: Specification;
try {
specFile = await load(filePath);
} catch (err) {
this.error(
new ValidationError({
type: 'invalid-file',
filepath: filePath,
})
);
}

if (specFile.isAsyncAPI3()) {
this.error('Optimize command does not support AsyncAPI v3 yet, please checkout https://github.com/asyncapi/optimizer/issues/168');
}

let optimizer: Optimizer;
let report: Report;
try {
specFile = await load(filePath);
optimizer = new Optimizer(specFile.text());
report = await optimizer.getReport();
} catch (err) {
Expand All @@ -65,7 +79,7 @@ export default class Optimize extends Command {
this.isInteractive = !flags['no-tty'];
this.optimizations = flags.optimization as Optimizations[];
this.outputMethod = flags.output as Outputs;

if (!(report.moveToComponents?.length || report.removeComponents?.length || report.reuseComponents?.length)) {
this.log(`No optimization has been applied since ${specFile.getFilePath() ?? specFile.getFileURL()} looks optimized!`);
return;
Expand All @@ -76,13 +90,13 @@ export default class Optimize extends Command {
await this.interactiveRun(report);
}

try {
try {
const optimizedDocument = optimizer.getOptimizedDocument({rules: {
moveToComponents: this.optimizations.includes(Optimizations.MOVE_TO_COMPONETS),
removeComponents: this.optimizations.includes(Optimizations.REMOVE_COMPONENTS),
reuseComponents: this.optimizations.includes(Optimizations.REUSE_COMPONENTS)
}, output: Output.YAML});

const specPath = specFile.getFilePath();
let newPath = '';
if (specPath) {
Expand Down Expand Up @@ -149,7 +163,7 @@ export default class Optimize extends Command {
this.showOptimizations(report.reuseComponents);
choices.push({name: 'reuse components', value: Optimizations.REUSE_COMPONENTS});
}
const optimizationRes = await inquirer.prompt([{
const optimizationRes = await inquirer.prompt([{
name: 'optimization',
message: 'select the type of optimization that you want to apply:',
type: 'checkbox',
Expand All @@ -158,7 +172,7 @@ export default class Optimize extends Command {
}]);

this.optimizations = optimizationRes.optimization;

const outputRes = await inquirer.prompt([{
name: 'output',
message: 'where do you want to save the result:',
Expand Down
2 changes: 1 addition & 1 deletion src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class Validate extends Command {
if (watchMode) {
specWatcher({ spec: specFile, handler: this, handlerName: 'validate' });
}

await validate(this, specFile, flags);
}
}
17 changes: 15 additions & 2 deletions src/models/SpecificationFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
import path from 'path';
import { URL } from 'url';
import fetch from 'node-fetch';

import yaml from 'js-yaml';
import { loadContext } from './Context';
import { ErrorLoadingSpec } from '../errors/specification-file';
import { MissingContextFileError } from '../errors/context-error';
Expand Down Expand Up @@ -34,6 +34,19 @@ export class Specification {
}
}

isAsyncAPI3() {
const jsObj = this.toJson();
return jsObj.asyncapi === '3.0.0';
}

toJson(): Record<string, any> {
try {
return yaml.load(this.spec, {json: true}) as Record<string, any>;
} catch (e) {
return JSON.parse(this.spec);
}
}

text() {
return this.spec;
}
Expand Down Expand Up @@ -137,7 +150,7 @@ export async function load(filePathOrContextName?: string, loadType?: LoadType):
if (e instanceof MissingContextFileError) {
throw new ErrorLoadingSpec();
}

throw e;
}
}
Expand Down
15 changes: 15 additions & 0 deletions test/commands/bundle/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@ import path from 'path';
import { fileCleanup } from '../../testHelper';

const spec = fs.readFileSync('./test/commands/bundle/final-asyncapi.yaml', {encoding: 'utf-8'});
const asyncapiv3 = './test/specification-v3.yml';

function validateGeneratedSpec(filePath, spec) {
const generatedSPec = fs.readFileSync(path.resolve(filePath), { encoding: 'utf-8' });
return generatedSPec === spec;
}

describe('bundle', () => {
describe('should handle AsyncAPI v3 document correctly', () => {
test
.stderr()
.stdout()
.command([
'bundle',
asyncapiv3,
'--output=./test/commands/bundle/final.yaml'])
.it('give error', (ctx, done) => {
expect(ctx.stderr).toEqual('Error: One of the files you tried to bundle is AsyncAPI v3 format, the bundle command does not support it yet, please checkout https://github.com/asyncapi/bundler/issues/133\n');
expect(ctx.stdout).toEqual('');
done();
});
});
test
.stdout()
.command([
Expand Down
Loading

0 comments on commit 94790a8

Please sign in to comment.