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: integrate v2 ParserJS in validate command #376

Merged
merged 5 commits into from
Jan 31, 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
10,063 changes: 6,127 additions & 3,936 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"@asyncapi/diff": "^0.4.1",
"@asyncapi/generator": "^1.9.12",
"@asyncapi/modelina": "^1.0.0-next.40",
"@asyncapi/parser": "^1.17.1",
"@asyncapi/parser": "^2.0.0-next-major.11",
"@asyncapi/studio": "^0.15.4",
"@oclif/core": "^1.18.0",
"@oclif/errors": "^1.3.5",
"@oclif/plugin-not-found": "^2.3.1",
"@stoplight/spectral-cli": "6.6.0",
"ajv": "^8.12.0",
"chalk": "^4.1.0",
"chokidar": "^3.5.2",
Expand Down Expand Up @@ -159,7 +160,8 @@
"pretest:coverage": "npm run build",
"release": "semantic-release",
"pretest": "npm run build",
"test": "cross-env NODE_ENV=development TEST=1 CONTEXT_FILENAME=\"./test.asyncapi\" CONTEXT_FILE_PATH=\"./\" jest --coverage -i",
"test": "npm run test:unit",
"test:unit": "cross-env NODE_ENV=development TEST=1 CONTEXT_FILENAME=\"./test.asyncapi\" CONTEXT_FILE_PATH=\"./\" jest --coverage -i",
"get-version": "echo $npm_package_version"
},
"types": "lib/index.d.ts"
Expand Down
45 changes: 29 additions & 16 deletions scripts/fetch-asyncapi-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ const request = require('request');
const fs = require('fs');
const unzipper = require('unzipper');
const path = require('path');
const parser = require('@asyncapi/parser');
const openapiSchemaParser = require('@asyncapi/openapi-schema-parser');
const avroSchemaParser = require('@asyncapi/avro-schema-parser');
const ramlDtSchemaParser = require('@asyncapi/raml-dt-schema-parser');

parser.registerSchemaParser(openapiSchemaParser);
parser.registerSchemaParser(avroSchemaParser);
parser.registerSchemaParser(ramlDtSchemaParser);
const { Parser } = require('@asyncapi/parser/cjs');
const { AvroSchemaParser } = require('@asyncapi/parser/cjs/schema-parser/avro-schema-parser');
const { OpenAPISchemaParser } = require('@asyncapi/parser/cjs/schema-parser/openapi-schema-parser');
const { RamlSchemaParser } = require('@asyncapi/parser/cjs/schema-parser/raml-schema-parser');

const parser = new Parser({
schemaParsers: [
AvroSchemaParser(),
OpenAPISchemaParser(),
RamlSchemaParser(),
]
});

const SPEC_EXAMPLES_ZIP_URL = 'https://github.com/asyncapi/spec/archive/refs/heads/master.zip';
const EXAMPLE_DIRECTORY = path.join(__dirname, '../assets/examples');
Expand Down Expand Up @@ -58,20 +63,19 @@ const buildCLIListFromExamples = async () => {
const files = fs.readdirSync(EXAMPLE_DIRECTORY);
const examples = files.filter(file => file.includes('.yml')).sort();

const listAllProtocolsForFile = (parsedAsyncAPI) => {
if (!parsedAsyncAPI.hasServers()) {return '';}
const servers = parsedAsyncAPI.servers();
return Object.keys(servers).map(server => servers[String(server)].protocol()).join(',');
};

const buildExampleList = examples.map(async example => {
const examplePath = path.join(EXAMPLE_DIRECTORY, example);
const exampleContent = fs.readFileSync(examplePath, { encoding: 'utf-8'});

try {
const parsedSpec = await parser.parse(exampleContent);
const title = parsedSpec.info().title();
const protocols = listAllProtocolsForFile(parsedSpec);
const { document } = await parser.parse(exampleContent);
// Failed for somereason to parse this spec file (document is undefined), ignore for now
if (!document) {
return;
}

const title = document.info().title();
const protocols = listAllProtocolsForFile(document);
return {
name: protocols ? `${title} - (protocols: ${protocols})` : title,
value: example
Expand All @@ -88,6 +92,15 @@ const buildCLIListFromExamples = async () => {
fs.writeFileSync(path.join(EXAMPLE_DIRECTORY, 'examples.json'), JSON.stringify(orderedExampleList, null, 4));
};

const listAllProtocolsForFile = (document) => {
const servers = document.servers();
if (servers.length === 0) {
return '';
}

return servers.all().map(server => server.protocol()).join(',');
};

const tidyup = async () => {
fs.unlinkSync(TEMP_ZIP_NAME);
};
Expand Down
37 changes: 29 additions & 8 deletions src/commands/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Flags } from '@oclif/core';
import * as diff from '@asyncapi/diff';
import AsyncAPIDiff from '@asyncapi/diff/lib/asyncapidiff';
import { promises as fs } from 'fs';
import * as parser from '../utils/parser';
import { load, Specification } from '../models/SpecificationFile';
import Command from '../base';
import { ValidationError } from '../errors/validation-error';
Expand All @@ -12,8 +11,11 @@ import {
DiffOverrideFileError,
DiffOverrideJSONError,
} from '../errors/diff-error';
import { specWatcher, specWatcherParams } from '../globals';
import { specWatcher } from '../globals';
import { watchFlag } from '../flags';
import { validationFlags, parse, convertToOldAPI } from '../parser';

import type { SpecWatcherParams } from '../globals';

const { readFile } = fs;

Expand All @@ -39,6 +41,7 @@ export default class Diff extends Command {
description: 'path to JSON file containing the override properties',
}),
watch: watchFlag(),
...validationFlags({ logDiagnostics: false }),
};

static args = [
Expand Down Expand Up @@ -107,7 +110,7 @@ export default class Diff extends Command {
this.error(err as Error);
}

let overrides = {};
let overrides: Awaited<ReturnType<typeof readOverrideFile>> = {};
if (overrideFilePath) {
try {
overrides = await readOverrideFile(overrideFilePath);
Expand All @@ -117,11 +120,14 @@ export default class Diff extends Command {
}

try {
const firstDocumentParsed = await parser.parse(firstDocument.text());
const secondDocumentParsed = await parser.parse(secondDocument.text());
const parsed = await parseDocuments(this, firstDocument, secondDocument, flags);
if (!parsed) {
return;
}

const diffOutput = diff.diff(
firstDocumentParsed.json(),
secondDocumentParsed.json(),
parsed.firstDocumentParsed.json(),
parsed.secondDocumentParsed.json(),
{
override: overrides,
outputType: outputFormat as diff.OutputType, // NOSONAR
Expand All @@ -144,6 +150,7 @@ export default class Diff extends Command {
});
}
}

outputJSON(diffOutput: AsyncAPIDiff, outputType: string) {
if (outputType === 'breaking') {
this.log(JSON.stringify(diffOutput.breaking(), null, 2));
Expand Down Expand Up @@ -173,6 +180,19 @@ export default class Diff extends Command {
}
}

async function parseDocuments(command: Command, firstDocument: Specification, secondDocument: Specification, flags: Record<string, any>) {
const { document: newFirstDocumentParsed, status: firstDocumentStatus } = await parse(command, firstDocument, flags);
const { document: newSecondDocumentParsed, status: secondDocumentStatus } = await parse(command, secondDocument, flags);

if (!newFirstDocumentParsed || !newSecondDocumentParsed || firstDocumentStatus === 'invalid' || secondDocumentStatus === 'invalid') {
return;
}

const firstDocumentParsed = convertToOldAPI(newFirstDocumentParsed);
const secondDocumentParsed = convertToOldAPI(newSecondDocumentParsed);
return { firstDocumentParsed, secondDocumentParsed };
}

/**
* Reads the file from give path and parses it as JSON
* @param path The path to override file
Expand All @@ -192,11 +212,12 @@ async function readOverrideFile(path: string): Promise<diff.OverrideObject> {
throw new DiffOverrideJSONError();
}
}

/**
* function to enable watchmode.
* The function is abstracted here, to avoid eslint cognitive complexity error.
*/
const enableWatch = (status: boolean, watcher: specWatcherParams) => {
const enableWatch = (status: boolean, watcher: SpecWatcherParams) => {
if (status) {
specWatcher(watcher);
}
Expand Down
6 changes: 3 additions & 3 deletions src/commands/generate/fromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default class Template extends Command {
}),
'map-base-url': Flags.string({
description: 'Maps all schema references from base url to local folder'
})
}),
};

static args = [
Expand All @@ -99,15 +99,14 @@ export default class Template extends Command {
mapBaseUrlToFolder: parsedFlags.mapBaseUrlToFolder,
disabledHooks: parsedFlags.disableHooks,
};

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 (watchTemplate) {
const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption);
await this.runWatchMode(asyncapi, template, output, watcherHandler);
Expand Down Expand Up @@ -263,6 +262,7 @@ export default class Template extends Command {
}
};
}

private getMapBaseUrlToFolderResolver = (urlToFolder: IMapBaseUrlToFlag) => {
return {
order: 1,
Expand Down
47 changes: 28 additions & 19 deletions src/commands/generate/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { CSharpFileGenerator, JavaFileGenerator, JavaScriptFileGenerator, TypeSc
import { Flags } from '@oclif/core';
import Command from '../../base';
import { load } from '../../models/SpecificationFile';
import { parse } from '../../utils/parser';
import { parse, validationFlags } from '../../parser';

import type { AbstractGenerator, AbstractFileGenerator } from '@asyncapi/modelina';

enum Languages {
typescript = 'typescript',
csharp = 'csharp',
Expand All @@ -14,6 +17,7 @@ enum Languages {
rust = 'rust'
}
const possibleLanguageValues = Object.values(Languages).join(', ');

export default class Models extends Command {
static description = 'Generates typed models';
static args = [
Expand Down Expand Up @@ -74,15 +78,20 @@ export default class Models extends Command {
description: 'C# specific, define the namespace to use for the generated models. This is required when language is `csharp`.',
required: false
}),
...validationFlags({ logDiagnostics: false }),
};

async run() {
const { args, flags } = await this.parse(Models);
const { tsModelType, tsEnumType, tsModuleSystem, tsExportType, namespace, packageName, output } = flags;
const { language, file } = args;

const inputFile = await load(file) || await load();
const parsedInput = await parse(inputFile.text());
const inputFile = (await load(file)) || (await load());
const { document, status } = await parse(this, inputFile, flags);
if (!document || status === 'invalid') {
return;
}

Logger.setLogger({
info: (message) => {
this.log(message);
Expand All @@ -97,8 +106,9 @@ export default class Models extends Command {
this.error(message);
},
});
let fileGenerator;
let fileOptions = {};

let fileGenerator: AbstractGenerator<any, any> & AbstractFileGenerator<any>;
let fileOptions: any = {};
switch (language) {
case Languages.typescript:
fileGenerator = new TypeScriptFileGenerator({
Expand Down Expand Up @@ -158,27 +168,26 @@ export default class Models extends Command {
default:
throw new Error(`Could not determine generator for language ${language}, are you using one of the following values ${possibleLanguageValues}?`);
}
let models;

if (output) {
models = await fileGenerator.generateToFiles(
parsedInput as any,
const models = await fileGenerator.generateToFiles(
document as any,
output,
{ ...fileOptions, } as any);
const generatedModels = models.map((model) => { return model.modelName; });

this.log(`Successfully generated the following models: ${generatedModels.join(', ')}`);
} else {
models = await fileGenerator.generateCompleteModels(
parsedInput as any,
{ ...fileOptions } as any);
const generatedModels = models.map((model) => {
return `
return;
}

const models = await fileGenerator.generateCompleteModels(
document as any,
{ ...fileOptions } as any);
const generatedModels = models.map((model) => {
return `
## Model name: ${model.modelName}
${model.result}
`;
});

this.log(`Successfully generated the following models: ${generatedModels.join('\n')}`);
}
});
this.log(`Successfully generated the following models: ${generatedModels.join('\n')}`);
}
}
24 changes: 6 additions & 18 deletions src/commands/validate.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Flags } from '@oclif/core';
import * as parser from '../utils/parser';

import Command from '../base';
import { ValidationError } from '../errors/validation-error';
import { validate, validationFlags } from '../parser';
import { load } from '../models/SpecificationFile';
import { specWatcher } from '../globals';
import { watchFlag } from '../flags';
Expand All @@ -11,7 +11,8 @@ export default class Validate extends Command {

static flags = {
help: Flags.help({ char: 'h' }),
watch: watchFlag()
watch: watchFlag(),
...validationFlags(),
};

static args = [
Expand All @@ -21,26 +22,13 @@ export default class Validate extends Command {
async run() {
const { args, flags } = await this.parse(Validate); //NOSONAR
const filePath = args['spec-file'];
const watchMode = flags['watch'];
const watchMode = flags.watch;

const specFile = await load(filePath);
if (watchMode) {
specWatcher({ spec: specFile, handler: this, handlerName: 'validate' });
}

try {
if (specFile.getFilePath()) {
await parser.parse(specFile.text());
this.log(`File ${specFile.getFilePath()} successfully validated!`);
} else if (specFile.getFileURL()) {
await parser.parse(specFile.text());
this.log(`URL ${specFile.getFileURL()} successfully validated`);
}
} catch (error) {
throw new ValidationError({
type: 'parser-error',
err: error
});
}
await validate(this, specFile, flags);
}
}
Loading