Skip to content

Commit

Permalink
chore: new trait behaviour in v3 spec (#744)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergio Moya <1083296+smoya@users.noreply.github.com>
  • Loading branch information
magicmatatjahu and smoya committed Jul 20, 2023
1 parent 00f59e4 commit 2000140
Show file tree
Hide file tree
Showing 8 changed files with 444 additions and 54 deletions.
35 changes: 10 additions & 25 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"prepublishOnly": "npm run generate:assets"
},
"dependencies": {
"@asyncapi/specs": "^5.1.0",
"@asyncapi/specs": "^6.0.0-next-major-spec.2",
"@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0",
"@stoplight/json-ref-resolver": "^3.1.5",
"@stoplight/spectral-core": "^1.16.1",
Expand Down
72 changes: 55 additions & 17 deletions src/custom-operations/apply-traits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,54 @@ const v2TraitPaths = [
];

export function applyTraitsV2(asyncapi: v2.AsyncAPIObject) {
applyAllTraits(asyncapi, v2TraitPaths);
applyAllTraitsV2(asyncapi, v2TraitPaths);
}

function applyAllTraitsV2(asyncapi: Record<string, any>, paths: string[]) {
const visited: Set<unknown> = new Set();
paths.forEach(path => {
JSONPath({
path,
json: asyncapi,
resultType: 'value',
callback(value) {
if (visited.has(value)) {
return;
}
visited.add(value);
applyTraitsToObjectV2(value);
},
});
});
}

function applyTraitsToObjectV2(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)]);
}
}
}
}

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

export function applyTraitsV3(asyncapi: v3.AsyncAPIObject) {
applyAllTraits(asyncapi, v3TraitPaths);
applyAllTraitsV3(asyncapi, v3TraitPaths);
}

function applyAllTraits(asyncapi: Record<string, any>, paths: string[]) {
function applyAllTraitsV3(asyncapi: Record<string, any>, paths: string[]) {
const visited: Set<unknown> = new Set();
paths.forEach(path => {
JSONPath({
Expand All @@ -48,18 +76,28 @@ function applyAllTraits(asyncapi: Record<string, any>, paths: string[]) {
return;
}
visited.add(value);
applyTraits(value);
applyTraitsToObjectV3(value);
},
});
});
}

function applyTraits(value: Record<string, unknown> & { traits?: any[] }) {
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)]);
}
function applyTraitsToObjectV3(value: Record<string, unknown>) {
if (!Array.isArray(value.traits)) {
return;
}

// shallow copy of object
const copy = { ...value };
// reset the object but preserve the reference
for (const key in value) {
delete value[key];
}

// merge root object at the end
for (const trait of [...copy.traits as any[], copy]) {
for (const key in trait) {
value[String(key)] = mergePatch(value[String(key)], trait[String(key)]);
}
}
}
}
23 changes: 19 additions & 4 deletions src/custom-operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { applyTraitsV2 } from './apply-traits';
import { resolveCircularRefs } from './resolve-circular-refs';
import { applyTraitsV2, applyTraitsV3 } from './apply-traits';
import { checkCircularRefs } from './check-circular-refs';
import { parseSchemasV2 } from './parse-schema';
import { anonymousNaming } from './anonymous-naming';
import { resolveCircularRefs } from './resolve-circular-refs';

import type { RulesetFunctionContext } from '@stoplight/spectral-core';
import type { Parser } from '../parser';
import type { ParseOptions } from '../parse';
import type { AsyncAPIDocumentInterface } from '../models';
import type { DetailedAsyncAPI } from '../types';
import { v2 } from 'spec-types';
import type { v2, v3 } from '../spec-types';

export async function customOperations(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, inventory: RulesetFunctionContext['documentInventory'], options: ParseOptions): Promise<void> {
switch (detailed.semver.major) {
case 2: return operationsV2(parser, document, detailed, inventory, options);
// case 3: return operationsV3(parser, document, detailed, options);
case 3: return operationsV3(parser, document, detailed, inventory, options);
}
}

async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, inventory: RulesetFunctionContext['documentInventory'], options: ParseOptions): Promise<void> {
checkCircularRefs(document);

if (options.applyTraits) {
applyTraitsV2(detailed.parsed as v2.AsyncAPIObject);
}
Expand All @@ -32,3 +35,15 @@ async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface,
anonymousNaming(document);
}

async function operationsV3(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, inventory: RulesetFunctionContext['documentInventory'], options: ParseOptions): Promise<void> {
checkCircularRefs(document);

if (options.applyTraits) {
applyTraitsV3(detailed.parsed as v3.AsyncAPIObject);
}
// TODO: Support schema parsing in v3
// if (options.parseSchemas) {
// await parseSchemasV2(parser, detailed);
// }
anonymousNaming(document);
}
6 changes: 3 additions & 3 deletions src/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
import type { AsyncAPIDocumentInterface } from './models';
import type { OldAsyncAPIDocument } from './old-api';
import type { DetailedAsyncAPI, AsyncAPIObject } from './types';
import { v2 } from 'spec-types';
import { v2, v3 } from 'spec-types';

export function createAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface {
switch (asyncapi.semver.major) {
case 2:
return new AsyncAPIDocumentV2(asyncapi.parsed as v2.AsyncAPIObject, { asyncapi, pointer: '/' });
// case 3:
// return new AsyncAPIDocumentV3(asyncapi.parsed, { asyncapi, pointer: '/' });
case 3:
return new AsyncAPIDocumentV3(asyncapi.parsed as v3.AsyncAPIObject, { asyncapi, pointer: '/' });
default:
throw new Error(`Unsupported AsyncAPI version: ${asyncapi.semver.version}`);
}
Expand Down
Loading

0 comments on commit 2000140

Please sign in to comment.