Skip to content

Commit

Permalink
refactor: init custom parser functionality (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored and derberg committed Oct 4, 2022
1 parent 5427244 commit 0f43554
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 12 deletions.
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 = [
// 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;
}

0 comments on commit 0f43554

Please sign in to comment.