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

refactor: init custom parser functionality #489

Merged
merged 2 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/jest": "^27.4.1",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.179",
"conventional-changelog-conventionalcommits": "^4.2.3",
"cross-env": "^7.0.3",
"eslint": "^7.27.0",
Expand All @@ -50,7 +51,9 @@
"@stoplight/spectral-core": "^1.10.1",
"@stoplight/spectral-functions": "^1.5.1",
"@stoplight/spectral-parsers": "^1.0.1",
"@stoplight/spectral-rulesets": "^1.4.3"
"@stoplight/spectral-rulesets": "^1.4.3",
"jsonpath-plus": "^6.0.1",
"lodash": "^4.17.21"
},
"release": {
"branches": [
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export const xParserSpecStringified = 'x-parser-spec-stringified';
export const xParserMessageName = 'x-parser-message-name';
export const xParserSchemaId = 'x-parser-schema-id';

export const xParserOriginalSchema = 'x-parser-original-schema';
export const xParserOriginalSchemaFormat = 'x-parser-original-schema-format';
export const xParserOriginalTraits = 'x-parser-original-traits';

Expand Down
60 changes: 60 additions & 0 deletions src/custom-operations/apply-traits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { JSONPath } from 'jsonpath-plus';

import { xParserOriginalTraits } from '../constants';
import { mergePatch } from '../utils';

const v2TraitPaths = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v2 means v2 of the spec here? If so, shall we extract the paths to their own files so we can do the same for v3 spec ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will have only 2 custom operations and I don't think so that we should split them to separate files. We can do it later, if we will have more logic for each version.

// operations
'$.channels.*.[publish,subscribe]',
'$.components.channels.*.[publish,subscribe]',
// messages
'$.channels.*.[publish,subscribe].message',
'$.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.channels.*.[publish,subscribe].message',
'$.components.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.messages.*',
];

export function applyTraitsV2(asyncapi: Record<string, unknown>) {
applyAllTraits(asyncapi, v2TraitPaths);
}

const v3TraitPaths = [
// operations
'$.channels.*.[publish,subscribe]',
'$.components.channels.*.[publish,subscribe]',
// messages
'$.channels.*.[publish,subscribe].message',
'$.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.channels.*.[publish,subscribe].message',
'$.components.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.messages.*',
];

export function applyTraitsV3(asyncapi: Record<string, unknown>) {
applyAllTraits(asyncapi, v3TraitPaths);
}

function applyAllTraits(asyncapi: Record<string, unknown>, paths: string[]) {
paths.forEach(path => {
JSONPath({
path,
json: asyncapi,
resultType: 'value',
callback(value) { applyTraits(value); },
});
});
}

function applyTraits(value: Record<string, unknown>) {
if (Array.isArray(value.traits)) {
for (const trait of value.traits) {
for (const key in trait) {
value[String(key)] = mergePatch(value[String(key)], trait[String(key)]);
}
}

value[xParserOriginalTraits] = value.traits;
delete value.traits;
}
}
27 changes: 27 additions & 0 deletions src/custom-operations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { applyTraitsV2, applyTraitsV3 } from './apply-traits';
import { parseSchemasV2 } from './parse-schema';

import type { ParseOptions } from "../parse";
import type { DetailedAsyncAPI } from "../types";

export async function customOperations(detailed: DetailedAsyncAPI, options: ParseOptions): Promise<void> {
switch (detailed.semver.major) {
case 2: return operationsV2(detailed, options);
case 3: return operationsV3(detailed, options);
}
}

async function operationsV2(detailed: DetailedAsyncAPI, options: ParseOptions): Promise<void> {
if (options.applyTraits) {
applyTraitsV2(detailed.parsed);
}
if (options.parseSchemas) {
await parseSchemasV2(detailed);
}
}

async function operationsV3(detailed: DetailedAsyncAPI, options: ParseOptions): Promise<void> {
if (options.applyTraits) {
applyTraitsV3(detailed.parsed);
}
}
66 changes: 66 additions & 0 deletions src/custom-operations/parse-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { JSONPath } from 'jsonpath-plus';
import { toPath } from 'lodash';

import { parseSchema, getDefaultSchemaFormat } from '../schema-parser';
import { xParserOriginalSchemaFormat } from '../constants';

import type { ParseSchemaInput } from "../schema-parser";
import type { DetailedAsyncAPI } from "../types";

interface ToParseItem {
input: ParseSchemaInput;
value: any;
}

const customSchemasPathsV2 = [
'$.channels.*.[publish,subscribe].message',
'$.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.channels.*.[publish,subscribe].message',
'$.components.channels.*.[publish,subscribe].message.oneOf.*',
'$.components.messages.*',
];

export async function parseSchemasV2(detailed: DetailedAsyncAPI) {
const defaultSchemaFormat = getDefaultSchemaFormat(detailed.parsed.asyncapi as string);
const parseItems: Array<ToParseItem> = [];

const visited: Set<unknown> = new Set();
customSchemasPathsV2.forEach(path => {
JSONPath({
path,
json: detailed.parsed,
resultType: 'all',
callback(result) {
const value = result.value;
if (visited.has(value)) {
return;
}
visited.add(value);

const payload = value.payload;
if (!payload) {
return;
}

parseItems.push({
input: {
asyncapi: detailed,
data: payload,
meta: undefined,
path: [...toPath(result.path.slice(1)), 'payload'],
schemaFormat: value.schemaFormat || defaultSchemaFormat,
defaultSchemaFormat,
},
value,
});
},
});
});

return Promise.all(parseItems.map(parseSchemaV2));
}

async function parseSchemaV2(item: ToParseItem) {
item.value[xParserOriginalSchemaFormat] = item.input.schemaFormat;
item.value.payload = await parseSchema(item.input);
}
7 changes: 6 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export { lint, validate } from './lint';
export { parse } from './parse';
export { stringify, unstringify } from './stringify';

export { registerSchemaParser } from './schema-parser';
export { AsyncAPISchemaParser } from './schema-parser/asyncapi-schema-parser';

export type { LintOptions, ValidateOptions, ValidateOutput } from './lint';
export type { StringifyOptions } from './stringify';
export type { ParseOptions } from './parse';
export type { ParserInput, ParserOutput, Diagnostic } from './types';
export type { AsyncAPISemver, ParserInput, ParserOutput, Diagnostic, SchemaValidateResult } from './types';

export type { ValidateSchemaInput, ParseSchemaInput, SchemaParser } from './schema-parser'
29 changes: 24 additions & 5 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { AsyncAPIDocumentInterface, newAsyncAPIDocument } from "./models";
import { normalizeInput, toAsyncAPIDocument } from "./utils";

import { customOperations } from './custom-operations';
import { validate } from "./lint";
import { stringify, unstringify } from './stringify';
import { createDetailedAsyncAPI, normalizeInput, toAsyncAPIDocument } from "./utils";

import { xParserSpecParsed } from './constants';

import type { ParserInput, ParserOutput } from './types';
import type { ValidateOptions } from './lint';

export interface ParseOptions {
applyTraits?: boolean;
parseSchemas?: boolean;
validateOptions?: ValidateOptions;
}

Expand All @@ -33,15 +39,24 @@ export async function parse(asyncapi: ParserInput, options?: ParseOptions): Prom
};
}

const parsed = newAsyncAPIDocument(validated as Record<string, unknown>);
const doc = {
...(validated as Record<string, any>),
[xParserSpecParsed]: true,
}
const parsed = unstringify(stringify(doc))?.json()!;

const detailed = createDetailedAsyncAPI(asyncapi as string | Record<string, unknown>, parsed);
await customOperations(detailed, options);
const parsedDoc = newAsyncAPIDocument(parsed);

return {
source: asyncapi,
parsed,
parsed: parsedDoc,
diagnostics,
};
} catch(err) {
// TODO: throw proper error
throw Error();
throw err;
}
}

Expand All @@ -55,10 +70,14 @@ function normalizeOptions(options?: ParseOptions): ParseOptions {
// shall copy
options = { ...defaultOptions, ...options };

// traits
// applyTraits
if (options.applyTraits === undefined) {
options.applyTraits = true;
}
// parseSchemas
if (options.parseSchemas === undefined) {
options.parseSchemas = true;
}

return options;
}
33 changes: 33 additions & 0 deletions src/schema-parser/asyncapi-schema-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SchemaParser } from "../schema-parser";

export function AsyncAPISchemaParser(): SchemaParser {
return {
validate,
parse,
getMimeTypes,
}
}

function validate() {

}

function parse() {

}

function getMimeTypes() {
const mimeTypes = [
'application/schema;version=draft-07',
'application/schema+json;version=draft-07',
'application/schema+yaml;version=draft-07',
];
['2.0.0', '2.1.0', '2.2.0', '2.3.0'].forEach(version => {
mimeTypes.push(
`application/vnd.aai.asyncapi;version=${version}`,
`application/vnd.aai.asyncapi+json;version=${version}`,
`application/vnd.aai.asyncapi+yaml;version=${version}`,
);
});
return mimeTypes;
}