Skip to content

Commit

Permalink
feat: new trait behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu committed Apr 2, 2023
1 parent f804bdb commit 37cdace
Show file tree
Hide file tree
Showing 7 changed files with 443 additions and 29 deletions.
17 changes: 13 additions & 4 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 @@ -43,7 +43,7 @@
"release": "semantic-release"
},
"dependencies": {
"@asyncapi/specs": "^3.1.0",
"@asyncapi/specs": "^5.0.0-next-major-spec.18",
"@openapi-contrib/openapi-schema-to-json-schema": "^3.2.0",
"@stoplight/json-ref-resolver": "^3.1.4",
"@stoplight/spectral-core": "^1.14.1",
Expand Down
2 changes: 1 addition & 1 deletion src/custom-operations/anonymous-naming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function assignNameToAnonymousMessages(document: AsyncAPIDocumentInterface) {
let anonymousMessageCounter = 0;
document.messages().forEach(message => {
if (message.name() === undefined && message.extensions().get(xParserMessageName)?.value() === undefined) {
setExtension(xParserMessageName, `<anonymous-message-${++anonymousMessageCounter}>`, message);
setExtension(xParserMessageName, message.id() || `<anonymous-message-${++anonymousMessageCounter}>`, message);
}
});
}
Expand Down
83 changes: 64 additions & 19 deletions src/custom-operations/apply-traits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { JSONPath } from 'jsonpath-plus';

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

import type { v2 } from '../spec-types';
import type { v2, v3 } from '../spec-types';

const v2TraitPaths = [
// operations
Expand All @@ -17,42 +17,87 @@ 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: v2.AsyncAPIObject) { // TODO: Change type when we will have implemented types for v3
applyAllTraits(asyncapi, v3TraitPaths);
export function applyTraitsV3(asyncapi: v3.AsyncAPIObject) {
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({
path,
json: asyncapi,
resultType: 'value',
callback(value) { applyTraits(value); },
callback(value) {
if (visited.has(value)) {
return;
}
visited.add(value);
applyTraitsToObjectV3(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)]);
}
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)]);
}
}
}
}
20 changes: 16 additions & 4 deletions src/custom-operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { applyTraitsV2 } from './apply-traits';
import { applyTraitsV2, applyTraitsV3 } from './apply-traits';
import { checkCircularRefs } from './check-circular-refs';
import { parseSchemasV2 } from './parse-schema';
import { anonymousNaming } from './anonymous-naming';
Expand All @@ -7,24 +7,36 @@ import type { Parser } from '../parser';
import type { ParseOptions } from '../parse';
import type { AsyncAPIDocumentInterface } from '../models';
import type { DetailedAsyncAPI } from '../types';
import type { v2 } from '../spec-types';
import type { v2, v3 } from '../spec-types';

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

async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise<void> {
checkCircularRefs(document);
anonymousNaming(document);

if (options.applyTraits) {
applyTraitsV2(detailed.parsed as v2.AsyncAPIObject);
}
if (options.parseSchemas) {
await parseSchemasV2(parser, detailed);
}
anonymousNaming(document);
}

async function operationsV3(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, 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);
}
Loading

0 comments on commit 37cdace

Please sign in to comment.