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

feat: initial structure+first approach #3

10 changes: 10 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
test/temp
output
__transpiled
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
dist
local
.vscode
.idea
coverage
*.DS_Store
109 changes: 109 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
env:
node: true
es6: true
mocha: true

plugins:
- sonarjs
- mocha
- security

extends:
- plugin:sonarjs/recommended
- plugin:mocha/recommended
- plugin:security/recommended

parserOptions:
ecmaVersion: 2018

rules:
# Ignore Rules
strict: 0
no-underscore-dangle: 0
no-mixed-requires: 0
no-process-exit: 0
no-warning-comments: 0
curly: 0
no-multi-spaces: 0
no-alert: 0
consistent-return: 0
consistent-this: [0, self]
func-style: 0
max-nested-callbacks: 0
camelcase: 0

# Warnings
no-debugger: 1
no-empty: 1
no-invalid-regexp: 1
no-unused-expressions: 1
no-native-reassign: 1
no-fallthrough: 1
sonarjs/cognitive-complexity: 1

# Errors
eqeqeq: 2
no-undef: 2
no-dupe-keys: 2
no-empty-character-class: 2
no-self-compare: 2
valid-typeof: 2
no-unused-vars: [2, { "args": "none" }]
handle-callback-err: 2
no-shadow-restricted-names: 2
no-new-require: 2
no-mixed-spaces-and-tabs: 2
block-scoped-var: 2
no-else-return: 2
no-throw-literal: 2
no-void: 2
radix: 2
wrap-iife: [2, outside]
no-shadow: 0
no-use-before-define: [2, nofunc]
no-path-concat: 2
valid-jsdoc: [0, {requireReturn: false, requireParamDescription: false, requireReturnDescription: false}]

# stylistic errors
no-spaced-func: 2
semi-spacing: 2
quotes: [2, 'single']
key-spacing: [2, { beforeColon: false, afterColon: true }]
indent: [2, 2]
no-lonely-if: 2
no-floating-decimal: 2
brace-style: [2, 1tbs, { allowSingleLine: true }]
comma-style: [2, last]
no-multiple-empty-lines: [2, {max: 1}]
no-nested-ternary: 2
operator-assignment: [2, always]
padded-blocks: [2, never]
quote-props: [2, as-needed]
keyword-spacing: [2, {'before': true, 'after': true, 'overrides': {}}]
space-before-blocks: [2, always]
array-bracket-spacing: [2, never]
computed-property-spacing: [2, never]
space-in-parens: [2, never]
space-unary-ops: [2, {words: true, nonwords: false}]
wrap-regex: 2
linebreak-style: 0
semi: [2, always]
arrow-spacing: [2, {before: true, after: true}]
no-class-assign: 2
no-const-assign: 2
no-dupe-class-members: 2
no-this-before-super: 2
no-var: 2
object-shorthand: [2, always]
prefer-arrow-callback: 2
prefer-const: 2
prefer-spread: 2
prefer-template: 2

overrides:
- files: "test/**"
rules:
prefer-arrow-callback: 0
sonarjs/no-duplicate-string: 0
security/detect-object-injection: 0
security/detect-non-literal-fs-filename: 0
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/node_modules/
dist
local
.vscode
.idea
coverage
*.DS_Store
65 changes: 65 additions & 0 deletions lib/appRelationsDiscovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const {validate} = require('./utils');

const supportedSyntax = ['default','mermaid'];

// eslint-disable-next-line sonarjs/cognitive-complexity
async function getRelations(asyncApiDocs,requestedSyntax = 'default') {
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
if (typeof asyncApiDocs !== 'object') throw new Error('You must pass an array of AsyncApiDocuments on which you wish to discover the relations between');
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
if (!supportedSyntax.includes(requestedSyntax)) return;
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved

const parsedAsyncApiDocs = await validate(asyncApiDocs);
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
const metrics = new Map();

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

for (const [, credentials] of Object.entries(servers)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need only values you can use .values() function

Suggested change
for (const [, credentials] of Object.entries(servers)) {
for (const credentials of Object.values(servers)) {

Copy link
Collaborator Author

@arjungarg07 arjungarg07 Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it like for (const [serverName, credentials] of Object.entries(servers)) and was thinking to add serverName in const slug = `${credentials.url()},${credentials.protocol()} so as to make it more unique. Like what if two servers named 'production' and 'development' have the same url and protocol, in that case, slug would remain same and services would double up in the sub, pub Array.
But due to lint issue, removed that for now :)

So do we have to make slug more unique having the names of servers concatenated too like:
slug = serverName,${credentials.url()},${credentials.protocol()}

Copy link
Collaborator

@magicmatatjahu magicmatatjahu Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But serverName is only a name for server, it's something like metadata for server, it can be named in different way in other spec, so as you wrote you can end with double items in map for this same server. I think that we should operate only on url and protocol. From your comment I also find some problems, because someone can describe server's url with parameters like server/{parameter} and define for this parameter some enum values. Not now, but in the next weeks (maybe at the end of gsoc) we should check if this server is exactly this same as in another spec with given possible enum. I hope that you understand :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should operate only on url and protocol
someone can describe server's url with parameters like server/{parameter} and define for this parameter some enum values.

Yeah! you're right. I just checked out the specs and saw that we could have it like this(server/{parameter}) too.

servers:
- url: '{username}.gigantic-server.com:{port}/{basePath}'
  description: The production API server
  protocol: secure-mqtt
  variables:
    username:
      # note! no enum here means it is an open value
      default: demo
      description: This value is assigned by the service provider, in this example `gigantic-server.com`
    port:
      enum:
        - '8883'
        - '8884'
      default: '8883'
    basePath:
      # open meaning there is the opportunity to use special base paths as assigned by the provider, default is `v2`
      default: v2

This might be a big concern coz like here, we have to configure the flow to support this case too, also need to iterate with multiple ports: 8883, 8884. I will keep this in mind, and would definitely fix it in the next weeks. For now, I think we can move forward. What do you suggest? 🤔

Shall I create an issue now only regarding this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you should create issue for that to not forget about it :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create issue for that and we can discuss about it in the new issue :)

Copy link
Collaborator Author

@arjungarg07 arjungarg07 Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you misunderstood me here. What you described is totally clear to me.

But, I was talking about a case like this one.

#one spec
  servers: 
    production:
        url: mqtt://localhost:1883
        protocol: mqtt
    development:
        url: mqtt://localhost:1883
        protocol: mqtt
  channels:
  someAnotherChannel: ...

If this is the case then we would iterate 2 times(for production and development) having the same slug and then double up the applications described for this AsyncAPI document.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaa ok, sorry :) But there you have also this same problem, because you will have doubled servers and you won't be able to connect in easy way this app with another app in another spec. I think that we should go first with only url and protocol and then think about serverName. You can create issue for that :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh sorry... I think about serverName again and again... Of course, you're right, in current logic you will have doubled pub/sub in your app, but I don't think so that it should be hard to fix, but not now :) You can create issue for that and handle it in the next PRs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it could be a potential bug coz user should never have define the spec having two servers with same url and protocol in the first place. Would open an issue for the same 👍🏽

const slug = `${credentials.url()},${credentials.protocol()}`;
let relation;
if (metrics.has(slug)) {
relation = metrics.get(slug);
} else {
relation = new Map();
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
}
// metrics
// ServerMap(n-servers) => {
// <Url+protocol> => ChannelMap(n-channels) => {
// <channelName> => { sub: [] , pub: [] }
// },
// ....
// }
jonaslagoni marked this conversation as resolved.
Show resolved Hide resolved
if (doc.hasChannels()) {
doc.channelNames().forEach((channelName) => {
let application;
if (relation.has(channelName)) {
application = relation.get(channelName);
} else {
application = {
sub: [],
pub: [],
};
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
}

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

if (channel.hasPublish()) {
application.pub.push(title);
}
if (channel.hasSubscribe()) {
application.sub.push(title);
}

relation.set(channelName, application);
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
});
}
metrics.set(slug,relation);
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
};
}
});
if (requestedSyntax === 'default')
return metrics;
};

module.exports = {getRelations};
3 changes: 3 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const appRelationsDiscovery = require('./appRelationsDiscovery');

module.exports = appRelationsDiscovery;
21 changes: 21 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const parser = require('@asyncapi/parser');

function validate(asyncApiDocs) {
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
return new Promise((resolve,reject) => {
const parsedDocs = [];
const Promises = [];
asyncApiDocs.forEach(document => {
Promises.push(new Promise((resolve, reject) => {
parser.parse(document.json())
.then(parsedDoc => parsedDocs.push(parsedDoc))
.then(() => resolve())
.catch(err => reject(err));
}));
});
Promise.all(Promises)
.then(() => resolve(parsedDocs))
.catch(err => reject(err));
});
arjungarg07 marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = { validate };
Loading