From 2d73557d927480f4d19817754e1c90e0d1f821e2 Mon Sep 17 00:00:00 2001 From: Viacheslav Turovskyi Date: Sat, 6 Apr 2024 11:05:29 +0000 Subject: [PATCH] feat: add `x-origin` property --- example/bundle-cjs.cjs | 2 +- example/bundle.ts | 6 +-- package.json | 3 +- src/index.ts | 15 +++---- src/parser.ts | 32 +++++--------- src/util.ts | 98 ------------------------------------------ src/v3/parser.ts | 35 +++++++-------- 7 files changed, 35 insertions(+), 156 deletions(-) diff --git a/example/bundle-cjs.cjs b/example/bundle-cjs.cjs index 06e7c91..30489c2 100644 --- a/example/bundle-cjs.cjs +++ b/example/bundle-cjs.cjs @@ -10,7 +10,7 @@ const bundle = require('@asyncapi/bundler'); async function main() { const document = await bundle([readFileSync('./main.yaml', 'utf-8')], { - referenceIntoComponents: false, + 'x-origin': true, }); if (document.yml()) { writeFileSync('asyncapi.yaml', document.yml()); diff --git a/example/bundle.ts b/example/bundle.ts index d33ad7e..6abf594 100644 --- a/example/bundle.ts +++ b/example/bundle.ts @@ -3,11 +3,9 @@ import bundle from '@asyncapi/bundler'; async function main() { const document = await bundle([readFileSync('./main.yaml', 'utf-8')], { - referenceIntoComponents: false, + referenceIntoComponents: true, }); - if (document.yml()) { - writeFileSync('asyncapi.yaml', document.yml()); - } + writeFileSync('asyncapi.yaml', document.yml()); } main().catch(e => console.error(e)); diff --git a/package.json b/package.json index 99143f2..1adf6b8 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,9 @@ "/lib" ], "dependencies": { - "@apidevtools/json-schema-ref-parser": "^9.1.2", + "@apidevtools/json-schema-ref-parser": "^11.5.4", "@asyncapi/parser": "^3.0.10", "@types/json-schema": "^7.0.11", - "axios": "^1.6.8", "js-yaml": "^4.1.0", "jsonpath-plus": "^6.0.1", "lodash": "^4.17.21" diff --git a/src/index.ts b/src/index.ts index 8b67b21..8fe3e2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,5 @@ -import { toJS, resolve, versionCheck, resolveBaseFileDir } from './util'; +import { toJS, resolve, versionCheck } from './util'; import { Document } from './document'; -import { parse } from './parser'; import type { AsyncAPIObject } from './spec-types'; import { resolveV3Document } from './v3/parser'; @@ -74,17 +73,13 @@ import { resolveV3Document } from './v3/parser'; * */ export default async function bundle(files: string[], options: any = {}) { - if (typeof options.base !== 'undefined') { - options.base = toJS(options.base); - await parse(options.base, options); - } + // if (typeof options.base !== 'undefined') { + // options.base = toJS(options.base); + // await parse(options.base, options); + // } const parsedJsons = files.map(file => toJS(file)) as AsyncAPIObject[]; - if (typeof options.baseDir !== 'undefined') { - parsedJsons.forEach(parsedJson => resolveBaseFileDir(parsedJson, options.baseDir)); - } - const majorVersion = versionCheck(parsedJsons); let resolvedJsons; diff --git a/src/parser.ts b/src/parser.ts index f4d23d0..023f4ce 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,42 +1,30 @@ import $RefParser from '@apidevtools/json-schema-ref-parser'; import { Parser } from '@asyncapi/parser'; -import { addXOrigins } from './util'; import { AsyncAPIObject } from 'spec-types'; const parser = new Parser(); /** - * Resolves external references and updates $refs. + * Fully dereferences the AsyncAPI Document. * @param {Object[]} JSONSchema * @private */ export async function parse(JSONSchema: AsyncAPIObject, options: any = {}) { let validationResult: any[] = []; - addXOrigins(JSONSchema); const dereferencedJSONSchema = await $RefParser.dereference(JSONSchema, { dereference: { circular: false, - excludedPathMatcher: (path: string): boolean => { - return ( - // prettier-ignore - !!(/#\/[a-zA-Z0-9]*/).exec(path)// || - // !!(/#\/channels\/[a-zA-Z0-9]*\/servers/).exec(path) || - // !!(/#\/operations\/[a-zA-Z0-9]*\/channel/).exec(path) || - // !!(/#\/operations\/[a-zA-Z0-9]*\/messages/).exec(path) || - // !!(/#\/operations\/[a-zA-Z0-9]*\/reply\/channel/).exec(path) || - // !!(/#\/operations\/[a-zA-Z0-9]*\/reply\/messages/).exec(path) || - // !!(/#\/components\/channels\/[a-zA-Z0-9]*\/servers/).exec(path) || - // !!(/#\/components\/operations\/[a-zA-Z0-9]*\/channel/).exec(path) || - // !!(/#\/components\/operations\/[a-zA-Z0-9]*\/messages/).exec(path) || - // !!(/#\/components\/operations\/[a-zA-Z0-9]*\/reply\/channel/).exec( - // path - // ) || - // !!(/#\/components\/operations\/[a-zA-Z0-9]*\/reply\/messages/).exec( - // path - // ) - ); + // excludedPathMatcher: (path: string): any => { + // return ( + // // prettier-ignore + // ); + // }, + onDereference: (path: string, value: AsyncAPIObject) => { + if (options['x-origin']) { + value['x-origin'] = path; + } }, }, }); diff --git a/src/util.ts b/src/util.ts index 6c2fa6f..60a12a2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,13 +1,8 @@ -import fs from 'fs'; -import axios from 'axios'; -import { merge } from 'lodash'; import yaml from 'js-yaml'; import { parse } from './parser'; import { ParserError } from './errors'; -import { JSONPath } from 'jsonpath-plus'; import type { AsyncAPIObject } from './spec-types'; -import path from 'path'; /** * @private @@ -60,12 +55,7 @@ export const resolve = async ( try { for (const asyncapiDocument of asyncapiDocuments) { - // if (options.referenceIntoComponents) { - // await parse(asyncapiDocument); - // } - addXOrigins(asyncapiDocument); // eslint-disable-line @typescript-eslint/no-use-before-define await parse(asyncapiDocument, options); - // const bundledAsyncAPIDocument = await $RefParser.bundle(asyncapiDocument); docs.push(asyncapiDocument); } } catch (e) {} @@ -101,91 +91,3 @@ export function versionCheck(asyncapiDocuments: AsyncAPIObject[]): number { export function isExternalReference(ref: string): boolean { return typeof ref === 'string' && !ref.startsWith('#'); } - -export function notAUrl(ref: string): boolean { - try { - new URL(ref); - return false; - } catch (error) { - return true; - } -} - -export function resolveBaseFileDir(file: object, baseFileDir: string) { - /** - * Update the local refences in a given file with the - * absolute file path using the baseDir passed by the - * user as an option. - */ - JSONPath({ - json: file, - resultType: 'all', - path: '$.channels.*.messages.*', - }).forEach( - ({ parent, parentProperty }: { parent: any; parentProperty: string }) => { - const ref = parent[String(parentProperty)]['$ref']; - if (isExternalReference(ref) && notAUrl(ref)) { - parent[String(parentProperty)]['$ref'] = path.resolve(baseFileDir, ref); - } - } - ); - - JSONPath({ - json: file, - resultType: 'all', - path: '$.operations.*.messages.*', - }).forEach( - ({ parent, parentProperty }: { parent: any; parentProperty: string }) => { - const ref = parent[String(parentProperty)]['$ref']; - if (isExternalReference(ref) && notAUrl(ref)) { - parent[String(parentProperty)]['$ref'] = path.resolve(baseFileDir, ref); - } - } - ); -} - -export function addXOrigins(asyncapiDocument: AsyncAPIObject) { - // VALUE from 'asyncapiDocument' becomes KEY for the - // underlying and recursive functions - Object.values(asyncapiDocument).forEach(async (key: any) => { - if (key && typeof key === 'object' && key !== '$ref') { - if (Object.keys(key).indexOf('$ref') !== -1) { - if (isExternalReference(key['$ref'])) { - key['x-origin'] = key['$ref']; - - // If an external `$ref` is found, the function goes into - // second-level recursion to see if there are more `$ref`s whose - // values need to be copied to the `x-origin` properties of the - // `$ref`ed file. - // If an external `$ref` is found again, the function goes into the - // third-level recursion, and so on, until it reaches a file that - // contains no external `$ref`s at all. - // Then it exits all the way up in the opposite direction. - - const inlineAsyncapiDocumentURI = key['$ref'].split('#/'); - const inlineAsyncapiDocumentPath = inlineAsyncapiDocumentURI[0]; - const inlineAsyncapiDocumentPointer = inlineAsyncapiDocumentURI[1]; - - let inlineAsyncapiDocument = inlineAsyncapiDocumentPath.startsWith( - 'http' - ) - ? yaml.load(await axios(inlineAsyncapiDocumentPath)) - : (yaml.load( - fs.readFileSync(inlineAsyncapiDocumentPath, 'utf-8') // eslint-disable-line - ) as any); // eslint-disable-line - - inlineAsyncapiDocument = - inlineAsyncapiDocument[String(inlineAsyncapiDocumentPointer)]; - - if (inlineAsyncapiDocument) { - addXOrigins(inlineAsyncapiDocument as AsyncAPIObject); - merge(key, inlineAsyncapiDocument); - } - } - } else { - addXOrigins(key); - } - } - }); - return asyncapiDocument; -} diff --git a/src/v3/parser.ts b/src/v3/parser.ts index ade2c82..af59646 100644 --- a/src/v3/parser.ts +++ b/src/v3/parser.ts @@ -1,6 +1,5 @@ import $RefParser from '@apidevtools/json-schema-ref-parser'; import { Parser } from '@asyncapi/parser'; -import { addXOrigins } from '../util'; import { AsyncAPIObject } from 'spec-types'; @@ -9,31 +8,29 @@ const parser = new Parser(); export async function parse(JSONSchema: AsyncAPIObject, options: any = {}) { let validationResult: any[] = []; - addXOrigins(JSONSchema); - const dereferencedJSONSchema = await $RefParser.dereference(JSONSchema, { dereference: { circular: false, - excludedPathMatcher: (path: string): boolean => { + excludedPathMatcher: (path: string): any => { return ( // prettier-ignore - !!(/#\/[a-zA-Z0-9]*/).exec(path) || - !!(/#\/channels\/[a-zA-Z0-9]*\/servers/).exec(path) || - !!(/#\/operations\/[a-zA-Z0-9]*\/channel/).exec(path) || - !!(/#\/operations\/[a-zA-Z0-9]*\/messages/).exec(path) || - !!(/#\/operations\/[a-zA-Z0-9]*\/reply\/channel/).exec(path) || - !!(/#\/operations\/[a-zA-Z0-9]*\/reply\/messages/).exec(path) || - !!(/#\/components\/channels\/[a-zA-Z0-9]*\/servers/).exec(path) || - !!(/#\/components\/operations\/[a-zA-Z0-9]*\/channel/).exec(path) || - !!(/#\/components\/operations\/[a-zA-Z0-9]*\/messages/).exec(path) || - !!(/#\/components\/operations\/[a-zA-Z0-9]*\/reply\/channel/).exec( - path - ) || - !!(/#\/components\/operations\/[a-zA-Z0-9]*\/reply\/messages/).exec( - path - ) + /#\/channels\/[a-zA-Z0-9]*\/servers/.test(path) || + /#\/operations\/[a-zA-Z0-9]*\/channel/.test(path) || + /#\/operations\/[a-zA-Z0-9]*\/messages/.test(path) || + /#\/operations\/[a-zA-Z0-9]*\/reply\/channel/.test(path) || + /#\/operations\/[a-zA-Z0-9]*\/reply\/messages/.test(path) || + /#\/components\/channels\/[a-zA-Z0-9]*\/servers/.test(path) || + /#\/components\/operations\/[a-zA-Z0-9]*\/channel/.test(path) || + /#\/components\/operations\/[a-zA-Z0-9]*\/messages/.test(path) || + /#\/components\/operations\/[a-zA-Z0-9]*\/reply\/channel/.test(path) || + /#\/components\/operations\/[a-zA-Z0-9]*\/reply\/messages/.test(path) ); }, + onDereference: (path: string, value: AsyncAPIObject) => { + if (options['x-origin']) { + value['x-origin'] = path; + } + }, }, });