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 new ParserJS version #925

Merged
merged 3 commits into from
Oct 7, 2022
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
7 changes: 4 additions & 3 deletions examples/asyncapi-from-parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parse } from '@asyncapi/parser';
import { Parser } from '@asyncapi/parser';
import { TypeScriptGenerator } from '../../src';

const generator = new TypeScriptGenerator();
Expand Down Expand Up @@ -30,8 +30,9 @@ const AsyncAPIDocument = {
};

export async function generate() : Promise<void> {
const parsedDoc = await parse(JSON.stringify(AsyncAPIDocument));
const models = await generator.generate(parsedDoc as any);
const parser = new Parser();
const { document } = await parser.parse(JSON.stringify(AsyncAPIDocument));
const models = await generator.generate(document as any);
for (const model of models) {
console.log(model.result);
}
Expand Down
6 changes: 6 additions & 0 deletions examples/integrate-with-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@
"test:watch": "react-scripts test --watchAll=false",
"eject": "react-scripts eject",
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache"
},
"jest": {
"moduleNameMapper": {
"^nimma/legacy$": "<rootDir>/../../node_modules/nimma/dist/legacy/cjs/index.js",
"^nimma/(.*)": "<rootDir>/../../node_modules/nimma/dist/cjs/$1"
}
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
}
}
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ module.exports = {
collectCoverageFrom: [
'src/**'
],
moduleNameMapper: {
'^nimma/legacy$': '<rootDir>/node_modules/nimma/dist/legacy/cjs/index.js',
'^nimma/(.*)': '<rootDir>/node_modules/nimma/dist/cjs/$1',
},
modulePathIgnorePatterns: [
'<rootDir>/examples/TEMPLATE',
'<rootDir>/test/generators/template'
Expand Down
2,368 changes: 1,805 additions & 563 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.9",
"@apidevtools/swagger-parser": "^10.0.3",
"@asyncapi/parser": "^1.17.0",
"@asyncapi/parser": "^2.0.0-next-major.5",
"alterschema": "^1.1.1",
"change-case": "^4.1.2",
"openapi-types": "9.3.0",
Expand Down
4 changes: 2 additions & 2 deletions src/models/ProcessorOptions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParserOptions } from '@asyncapi/parser';
import { ParseOptions } from '@asyncapi/parser';
import { TypeScriptInputProcessorOptions } from '../processors/index';

export interface ProcessorOptions {
asyncapi?: ParserOptions,
asyncapi?: ParseOptions,
typescript?: TypeScriptInputProcessorOptions,
}
121 changes: 75 additions & 46 deletions src/processors/AsyncAPIInputProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,62 @@
import {parse, AsyncAPIDocument, Schema as AsyncAPISchema, ParserOptions} from '@asyncapi/parser';
import { Parser } from '@asyncapi/parser';
import { AbstractInputProcessor } from './AbstractInputProcessor';
import { JsonSchemaInputProcessor } from './JsonSchemaInputProcessor';
import { InputMetaModel, ProcessorOptions } from '../models';
import { Logger } from '../utils';
import { AsyncapiV2Schema } from '../models/AsyncapiV2Schema';
import { convertToMetaModel } from '../helpers';

import type { AsyncAPIDocumentInterface, SchemaInterface as AsyncAPISchema } from '@asyncapi/parser';

/**
* Class for processing AsyncAPI inputs
*/
export class AsyncAPIInputProcessor extends AbstractInputProcessor {
private parser = new Parser();

static supportedVersions = ['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0'];

/**
* Process the input as an AsyncAPI document
*
* @param input
*/
async process(input: Record<string, any>, options?: ProcessorOptions): Promise<InputMetaModel> {
// eslint-disable-next-line sonarjs/cognitive-complexity
async process(input?: Record<string, any>, options?: ProcessorOptions): Promise<InputMetaModel> {
if (!this.shouldProcess(input)) {throw new Error('Input is not an AsyncAPI document so it cannot be processed.');}

Logger.debug('Processing input as an AsyncAPI document');
let doc: AsyncAPIDocument;
let doc: AsyncAPIDocumentInterface;
const inputModel = new InputMetaModel();
if (!AsyncAPIInputProcessor.isFromParser(input)) {
doc = await parse(input as any, options?.asyncapi || {} as ParserOptions);
const { document, diagnostics } = await this.parser.parse(input as any, options?.asyncapi || {});
if (document) {
doc = document;
} else {
const err = new Error('Input is not an correct AsyncAPI document so it cannot be processed.');
(err as any).diagnostics = diagnostics;
throw err;
}
} else {
doc = input as AsyncAPIDocument;
doc = input as AsyncAPIDocumentInterface;
}

inputModel.originalInput = doc;
// Go over all the message payloads and convert them to models
for (const [, message] of doc.allMessages()) {
const schema = AsyncAPIInputProcessor.convertToInternalSchema(message.payload());
const newCommonModel = JsonSchemaInputProcessor.convertSchemaToCommonModel(schema);
if (newCommonModel.$id !== undefined) {
if (inputModel.models[newCommonModel.$id] !== undefined) {
Logger.warn(`Overwriting existing model with $id ${newCommonModel.$id}, are there two models with the same id present?`, newCommonModel);
for (const message of doc.allMessages()) {
const payload = message.payload();
if (payload) {
const schema = AsyncAPIInputProcessor.convertToInternalSchema(payload);
const newCommonModel = JsonSchemaInputProcessor.convertSchemaToCommonModel(schema);
if (newCommonModel.$id !== undefined) {
if (inputModel.models[newCommonModel.$id] !== undefined) {
Logger.warn(`Overwriting existing model with $id ${newCommonModel.$id}, are there two models with the same id present?`, newCommonModel);
}
const metaModel = convertToMetaModel(newCommonModel);
inputModel.models[metaModel.name] = metaModel;
} else {
Logger.warn('Model did not have $id which is required, ignoring.', newCommonModel);
}
const metaModel = convertToMetaModel(newCommonModel);
inputModel.models[metaModel.name] = metaModel;
} else {
Logger.warn('Model did not have $id which is required, ignoring.', newCommonModel);
}
}
return inputModel;
Expand All @@ -54,14 +70,15 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
*
* @param schema to reflect name for
*/
/* eslint-disable @typescript-eslint/no-non-null-assertion */
// eslint-disable-next-line sonarjs/cognitive-complexity
static convertToInternalSchema(
schema: AsyncAPISchema | boolean,
alreadyIteratedSchemas: Map<string, AsyncapiV2Schema> = new Map()
): AsyncapiV2Schema | boolean {
if (typeof schema === 'boolean') {return schema;}

let schemaUid = schema.uid();
let schemaUid = schema.id();
//Because the constraint functionality of generators cannot handle -, <, >, we remove them from the id if it's an anonymous schema.
if (schemaUid.includes('<anonymous-schema')) {
schemaUid = schemaUid.replace('<', '').replace(/-/g, '_').replace('>', '');
Expand All @@ -75,63 +92,66 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
convertedSchema[this.MODELGEN_INFFERED_NAME] = schemaUid;
alreadyIteratedSchemas.set(schemaUid, convertedSchema);

if (schema.allOf() !== null) {
convertedSchema.allOf = schema.allOf().map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
if (schema.allOf()) {
convertedSchema.allOf = schema.allOf()!.map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
}
if (schema.oneOf() !== null) {
convertedSchema.oneOf = schema.oneOf().map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
if (schema.oneOf()) {
convertedSchema.oneOf = schema.oneOf()!.map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
}
if (schema.anyOf() !== null) {
convertedSchema.anyOf = schema.anyOf().map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
if (schema.anyOf()) {
convertedSchema.anyOf = schema.anyOf()!.map((item) => this.convertToInternalSchema(item, alreadyIteratedSchemas));
}
if (schema.not() !== null) {
convertedSchema.not = this.convertToInternalSchema(schema.not(), alreadyIteratedSchemas);
if (schema.not()) {
convertedSchema.not = this.convertToInternalSchema(schema.not()!, alreadyIteratedSchemas);
}
if (
typeof schema.additionalItems() === 'object' &&
schema.additionalItems() !== null
) {
convertedSchema.additionalItems = this.convertToInternalSchema(schema.additionalItems(), alreadyIteratedSchemas);
}
if (schema.contains() !== null) {
convertedSchema.contains = this.convertToInternalSchema(schema.contains(), alreadyIteratedSchemas);
if (schema.contains()) {
convertedSchema.contains = this.convertToInternalSchema(schema.contains()!, alreadyIteratedSchemas);
}
if (schema.propertyNames() !== null) {
convertedSchema.propertyNames = this.convertToInternalSchema(schema.propertyNames(), alreadyIteratedSchemas);
if (schema.propertyNames()) {
convertedSchema.propertyNames = this.convertToInternalSchema(schema.propertyNames()!, alreadyIteratedSchemas);
}
if (schema.if() !== null) {
convertedSchema.if = this.convertToInternalSchema(schema.if(), alreadyIteratedSchemas);
if (schema.if()) {
convertedSchema.if = this.convertToInternalSchema(schema.if()!, alreadyIteratedSchemas);
}
if (schema.then() !== null) {
convertedSchema.then = this.convertToInternalSchema(schema.then(), alreadyIteratedSchemas);
if (schema.then()) {
convertedSchema.then = this.convertToInternalSchema(schema.then()!, alreadyIteratedSchemas);
}
if (schema.else() !== null) {
convertedSchema.else = this.convertToInternalSchema(schema.else(), alreadyIteratedSchemas);
if (schema.else()) {
convertedSchema.else = this.convertToInternalSchema(schema.else()!, alreadyIteratedSchemas);
}
if (
typeof schema.additionalProperties() === 'object' &&
schema.additionalProperties() !== null
) {
convertedSchema.additionalProperties = this.convertToInternalSchema(schema.additionalProperties(), alreadyIteratedSchemas);
}
if (schema.items() !== null) {
if (schema.items()) {
if (Array.isArray(schema.items())) {
convertedSchema.items = (schema.items() as AsyncAPISchema[]).map((item) => this.convertToInternalSchema(item), alreadyIteratedSchemas);
} else {
convertedSchema.items = this.convertToInternalSchema(schema.items() as AsyncAPISchema, alreadyIteratedSchemas);
}
}

if (schema.properties() !== null && Object.keys(schema.properties()).length) {
const schemaProperties = schema.properties();
if (schemaProperties && Object.keys(schemaProperties).length) {
const properties : {[key: string]: AsyncapiV2Schema | boolean} = {};
for (const [propertyName, propertySchema] of Object.entries(schema.properties())) {
for (const [propertyName, propertySchema] of Object.entries(schemaProperties)) {
properties[String(propertyName)] = this.convertToInternalSchema(propertySchema, alreadyIteratedSchemas);
}
convertedSchema.properties = properties;
}
if (schema.dependencies() !== null && Object.keys(schema.dependencies()).length) {

const schemaDependencies = schema.dependencies();
if (schemaDependencies && Object.keys(schemaDependencies).length) {
const dependencies: { [key: string]: AsyncapiV2Schema | boolean | string[] } = {};
for (const [dependencyName, dependency] of Object.entries(schema.dependencies())) {
for (const [dependencyName, dependency] of Object.entries(schemaDependencies)) {
if (typeof dependency === 'object' && !Array.isArray(dependency)) {
dependencies[String(dependencyName)] = this.convertToInternalSchema(dependency, alreadyIteratedSchemas);
} else {
Expand All @@ -140,29 +160,36 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
}
convertedSchema.dependencies = dependencies;
}
if (schema.patternProperties() !== null && Object.keys(schema.patternProperties()).length) {

const schemaPatternProperties = schema.patternProperties();
if (schemaPatternProperties && Object.keys(schemaPatternProperties).length) {
const patternProperties: { [key: string]: AsyncapiV2Schema | boolean } = {};
for (const [patternPropertyName, patternProperty] of Object.entries(schema.patternProperties())) {
for (const [patternPropertyName, patternProperty] of Object.entries(schemaPatternProperties)) {
patternProperties[String(patternPropertyName)] = this.convertToInternalSchema(patternProperty, alreadyIteratedSchemas);
}
convertedSchema.patternProperties = patternProperties;
}
if (schema.definitions() !== null && Object.keys(schema.definitions()).length) {

const schemaDefinitions = schema.definitions();
if (schemaDefinitions && Object.keys(schemaDefinitions).length) {
const definitions: { [key: string]: AsyncapiV2Schema | boolean } = {};
for (const [definitionName, definition] of Object.entries(schema.definitions())) {
for (const [definitionName, definition] of Object.entries(schemaDefinitions)) {
definitions[String(definitionName)] = this.convertToInternalSchema(definition, alreadyIteratedSchemas);
}
convertedSchema.definitions = definitions;
}

return convertedSchema;
}
/* eslint-enable @typescript-eslint/no-non-null-assertion */

/**
* Figures out if an object is of type AsyncAPI document
*
* @param input
*/
shouldProcess(input: Record<string, any>) : boolean {
shouldProcess(input?: Record<string, any>) : boolean {
if (!input) {return false;}
const version = this.tryGetVersionOfDocument(input);
if (!version) {return false;}
return AsyncAPIInputProcessor.supportedVersions.includes(version);
Expand All @@ -173,7 +200,8 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
*
* @param input
*/
tryGetVersionOfDocument(input: Record<string, any>) : string | undefined {
tryGetVersionOfDocument(input?: Record<string, any>) : string | undefined {
if (!input) {return;}
if (AsyncAPIInputProcessor.isFromParser(input)) {
return input.version();
}
Expand All @@ -185,7 +213,8 @@ export class AsyncAPIInputProcessor extends AbstractInputProcessor {
*
* @param input
*/
static isFromParser(input: Record<string, any>): boolean {
static isFromParser(input?: Record<string, any>): boolean {
if (!input) {return false;}
if (input['_json'] !== undefined && input['_json'].asyncapi !== undefined &&
typeof input.version === 'function') {
return true;
Expand Down
Loading