Skip to content
This repository has been archived by the owner on Apr 11, 2024. It is now read-only.

feat: upgrade to AsyncApi 3 parser #191

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 69 additions & 51 deletions lib/appRelationsDiscovery.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,75 @@
const getMermaidFlowchart = require('./getMermaidFlowchart');
const getPlantUMLDiagram = require('./getPlantUMLDiagram');
const getReactFlowData = require('./getReactFlowData');
const {validate} = require('./utils');
const { validate } = require('./utils');

const supportedSyntax = ['default','mermaid','plantUML','reactFlow'];
const supportedSyntax = ['default', 'mermaid', 'plantUML', 'reactFlow'];
const defaultOptions = {
syntax: 'default',
};

function transformAsyncApiIntoRelationModel(apiDocument, metrics) {
for (const operation of apiDocument.operations()) {
for (const channel of operation.channels()) {
for (const server of channel.servers()) {
const relation = getOrCreateRelation(server, metrics);
const application = getOrCreateApplication(channel, relation);
addPubSubToApplication(apiDocument.info().title(), operation, application, channel);
}
}
}
return metrics;
}

function addPubSubToApplication(title, operation, application, channel) {
if (['send', 'subscribe'].includes(operation.action())) {
if (application.sub.has(title)) {
throw new Error(`${title} is already subscribed to ${channel}`);
}
application.sub.set(title, channel.json());
} else if (['receive', 'publish'].includes(operation.action())) {
if (application.pub.has(title)) {
throw new Error(`${title} is already publishing to ${channel}`);
}
application.pub.set(title, channel.json());
} else {
throw Error(`action ${operation.action()} not defined.`);
}
}

function getOrCreateApplication(channel, relation) {
let application;
const channelName = channel.address();
if (relation.has(channelName)) {
application = relation.get(channelName);
} else {
application = {
sub: new Map(),
pub: new Map(),
};
relation.set(channelName, application);
}
return application;
}

function getOrCreateRelation(server, metrics) {
let url = server.url();
if (!url.endsWith('/')) {
// AsyncApi version 2 returns url without trailing slash
url += '/';
}
const protocol = server.protocol();
const slug = `${url},${protocol}`;
let relation;
if (metrics.has(slug)) {
relation = metrics.get(slug);
} else {
relation = new Map();
metrics.set(slug, relation);
}
return relation;
}

/**
* Validates and analyzes a list of AsyncAPI documents and get applications described by those files
*
Expand All @@ -21,60 +83,16 @@ async function getRelations(asyncApiDocs, { syntax } = defaultOptions) {
if (!Array.isArray(asyncApiDocs)) throw new Error('You must pass an array of AsyncApiDocuments on which you wish to discover the relations between');
if (!supportedSyntax.includes(syntax)) throw new Error('The given syntax is not supported');

let parsedAsyncApiDocs;
let parsedAsyncApiDocs;
try {
parsedAsyncApiDocs = await validate(asyncApiDocs);
} catch (e) {
throw new Error(e);
}
const metrics = new Map();

parsedAsyncApiDocs.forEach(doc => {
if (doc.hasServers()) {
const servers = doc.servers();

for (const credentials of Object.values(servers)) {
const slug = `${credentials.url()},${credentials.protocol()}`;
let relation;
if (metrics.has(slug)) {
relation = metrics.get(slug);
} else {
relation = new Map();
metrics.set(slug,relation);
}

if (doc.hasChannels()) {
doc.channelNames().forEach((channelName) => {
let application;
if (relation.has(channelName)) {
application = relation.get(channelName);
} else {
application = {
sub: new Map(),
pub: new Map(),
};
relation.set(channelName, application);
}

const channel = doc.channel(channelName);
const title = doc.info().title();

if (channel.hasPublish()) {
if (application.pub.has(title)) {
throw new Error(`${title} is already publishing to ${channel}`);
}
application.pub.set(title,channel.json());
}
if (channel.hasSubscribe()) {
if (application.sub.has(title)) {
throw new Error(`${title} is already subscribed to ${channel}`);
}
application.sub.set(title,channel.json());
}
});
}
}
}
parsedAsyncApiDocs.forEach(doc => {
transformAsyncApiIntoRelationModel(doc.document, metrics);
});
switch (syntax) {
case 'mermaid':
Expand All @@ -83,9 +101,9 @@ async function getRelations(asyncApiDocs, { syntax } = defaultOptions) {
return getPlantUMLDiagram(metrics);
case 'reactFlow':
return getReactFlowData(metrics);
default:
default:
return metrics;
}
}

module.exports = {getRelations};
module.exports = { getRelations };
7 changes: 4 additions & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const parser = require('@asyncapi/parser');
const { Parser } = require('@asyncapi/parser');

/**
* Validate and parse given array of AsyncAPI documents.
Expand All @@ -9,10 +9,11 @@ const parser = require('@asyncapi/parser');

function validate(asyncApiDocs) {
return Promise.all(asyncApiDocs.map(async doc => {
if (typeof doc === 'object' && typeof doc.ext === 'function' && doc.ext('x-parser-spec-parsed')) {
if (typeof doc === 'object' && typeof doc.document.extensions === 'function' && doc.document.extensions().get('x-parser-spec-parsed').value()) {
return doc;
}
return parser.parse(doc);
const parser = new Parser();
return await parser.parse(doc);
}));
}

Expand Down
Loading
Loading