Skip to content

Commit

Permalink
feat: integrate v2 ParserJS in validate command (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Jan 31, 2023
1 parent c2cf56d commit fe5bf85
Show file tree
Hide file tree
Showing 16 changed files with 6,546 additions and 4,037 deletions.
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

0 comments on commit fe5bf85

Please sign in to comment.