From 2d648594ccbf0d654c45d1820d98c6f14e803def Mon Sep 17 00:00:00 2001
From: nikhilkalburgi
Date: Wed, 6 Mar 2024 23:47:14 +0530
Subject: [PATCH] add components except Message and schema
Signed-off-by: nikhilkalburgi
---
src/Asyncapi.ts | 132 +++++++++++++++++++++++++++++++++-
src/components/Asyncapi.ejs | 3 +-
src/components/Bindings.ejs | 2 +-
src/components/Extensions.ejs | 4 +-
src/components/Operations.ejs | 125 ++++++++++++++++++++++++++++++++
src/components/Security.ejs | 117 ++++++++++++++++++++++++++++++
src/components/Servers.ejs | 10 ++-
7 files changed, 380 insertions(+), 13 deletions(-)
diff --git a/src/Asyncapi.ts b/src/Asyncapi.ts
index ba39f72..2f8cf37 100644
--- a/src/Asyncapi.ts
+++ b/src/Asyncapi.ts
@@ -3,6 +3,7 @@ import * as vscode from 'vscode';
import * as ejs from 'ejs';
import * as path from 'path';
import * as Markdownit from 'markdown-it';
+import { Server } from 'http';
const md = Markdownit('commonmark');
@@ -10,6 +11,7 @@ const md = Markdownit('commonmark');
const extRenderType = 'x-schema-private-render-type';
const extRenderAdditionalInfo = 'x-schema-private-render-additional-info';
const extRawValue = 'x-schema-private-raw-value';
+const extParameterLocation = 'x-schema-private-parameter-location';
const jsonSchemaTypes: string[] = [
'string',
'number',
@@ -21,6 +23,30 @@ const jsonSchemaTypes: string[] = [
];
class SchemaHelper {
+ static parametersToSchema(parameters: any[]) {
+ if (parameters.length === 0) {
+ return;
+ }
+
+ const json:object = {
+ type: 'object',
+ properties: parameters.reduce(
+ (obj, parameter) => {
+ const parameterName = parameter.id();
+ obj[String(parameterName)] = Object.assign({}, parameter.schema() === undefined ? {type: 'string'} : parameter.schema().json());
+ obj[String(parameterName)].description =
+ parameter.description() || obj[String(parameterName)].description;
+ obj[String(parameterName)][extParameterLocation] = parameter.location();
+ return obj;
+ },
+ {},
+ ),
+ required: parameters.map(parameter => parameter.id()),
+ [extRenderType]: false,
+ [extRenderAdditionalInfo]: false,
+ };
+ return new SchemaModel(json);
+ }
static jsonFieldToSchema(value: any): object {
if (value === undefined || value === null) {
return {
@@ -102,6 +128,96 @@ class SchemaHelper {
}
}
+class ServerHelper {
+ static securityType(value: string) {
+ switch (value) {
+ case 'apiKey':
+ return 'API key';
+ case 'oauth2':
+ return 'OAuth2';
+ case 'openIdConnect':
+ return 'Open ID';
+ case 'http':
+ return 'HTTP';
+ case 'userPassword':
+ return 'User/Password';
+ case 'X509':
+ return 'X509';
+ case 'symmetricEncryption':
+ return 'Symmetric Encription';
+ case 'asymmetricEncryption':
+ return 'Asymmetric Encription';
+ case 'httpApiKey':
+ return 'HTTP API key';
+ case 'scramSha256':
+ return 'ScramSha256';
+ case 'scramSha512':
+ return 'ScramSha512';
+ case 'gssapi':
+ return 'GSSAPI';
+ case 'plain':
+ return 'PLAIN';
+ default:
+ return 'API key';
+ }
+ }
+
+ static flowName(value: string) {
+ switch (value) {
+ case 'implicit':
+ return 'Implicit';
+ case 'password':
+ return 'Password';
+ case 'clientCredentials':
+ return 'Client credentials';
+ case 'authorizationCode':
+ return 'Authorization Code';
+ default:
+ return 'Implicit';
+ }
+ }
+
+ static getKafkaSecurity(protocol: string, securitySchema: { type: () => any; }) {
+ let securityProtocol;
+ let saslMechanism;
+ if (protocol === 'kafka') {
+ if (securitySchema) {
+ securityProtocol = 'SASL_PLAINTEXT';
+ } else {
+ securityProtocol = 'PLAINTEXT';
+ }
+ } else if (securitySchema) {
+ securityProtocol = 'SASL_SSL';
+ } else {
+ securityProtocol = 'SSL';
+ }
+ if (securitySchema) {
+ switch (securitySchema.type()) {
+ case 'plain':
+ saslMechanism = 'PLAIN';
+ break;
+ case 'scramSha256':
+ saslMechanism = 'SCRAM-SHA-256';
+ break;
+ case 'scramSha512':
+ saslMechanism = 'SCRAM-SHA-512';
+ break;
+ case 'oauth2':
+ saslMechanism = 'OAUTHBEARER';
+ break;
+ case 'gssapi':
+ saslMechanism = 'GSSAPI';
+ break;
+ case 'X509':
+ securityProtocol = 'SSL';
+ break;
+ }
+ }
+
+ return { securityProtocol, saslMechanism };
+ }
+}
+
export default async function info(asyncapi:AsyncAPIDocumentInterface, context: vscode.ExtensionContext) {
@@ -128,7 +244,17 @@ export default async function info(asyncapi:AsyncAPIDocumentInterface, context:
},
servers:{
servers: asyncapi.servers(),
- schemaHelper: SchemaHelper
+ schemaHelper: SchemaHelper,
+ serverHelper: ServerHelper,
+ md
+ },
+ operations:{
+ channels: asyncapi.channels(),
+ isV3: asyncapi.version().split('.')[0] === '3',
+ schemaHelper: SchemaHelper,
+ serverHelper: ServerHelper,
+ allServersLength: asyncapi.servers().all().length,
+ md
},
path:{
infoPath: path.join(context.extensionPath,'dist', 'components','Info.ejs'),
@@ -137,7 +263,9 @@ export default async function info(asyncapi:AsyncAPIDocumentInterface, context:
securityPath: path.join(context.extensionPath,'dist', 'components','Security.ejs'),
bindingsPath: path.join(context.extensionPath,'dist', 'components','Bindings.ejs'),
extensionsPath: path.join(context.extensionPath,'dist', 'components','Extensions.ejs'),
- schemaPath: path.join(context.extensionPath,'dist', 'components','Schema.ejs')
+ schemaPath: path.join(context.extensionPath,'dist', 'components','Schema.ejs'),
+ operationsPath: path.join(context.extensionPath,'dist', 'components','Operations.ejs'),
+ messagePath: path.join(context.extensionPath,'dist', 'components','Message.ejs')
}
});
}
\ No newline at end of file
diff --git a/src/components/Asyncapi.ejs b/src/components/Asyncapi.ejs
index 43f3214..983bdf1 100644
--- a/src/components/Asyncapi.ejs
+++ b/src/components/Asyncapi.ejs
@@ -1,2 +1,3 @@
<%- include(path.infoPath,{...info,tagsPath: path.tagsPath}) %>
-<%- include(path.serversPath,{...servers, ...path}) %>
\ No newline at end of file
+<%- include(path.serversPath,{...servers, ...path}) %>
+<%- include(path.operationsPath,{...operations, ...path}) %>
\ No newline at end of file
diff --git a/src/components/Bindings.ejs b/src/components/Bindings.ejs
index 12df28e..c927f9c 100644
--- a/src/components/Bindings.ejs
+++ b/src/components/Bindings.ejs
@@ -1,5 +1,5 @@
<% if (!bindings.isEmpty()) { %>
<% for(let binding of bindings.all()){ %>
- <%- include(SchemaPath,{ schemaName:`${binding.protocol().charAt(0).toUpperCase() + binding.protocol().slice(1)} ${name}`, schema: schemaHelper.jsonToSchema(binding), key: binding.protocol() }) %>
+ <%- include(schemaPath,{ schemaName:`${binding.protocol().charAt(0).toUpperCase() + binding.protocol().slice(1)} ${name}`, schema: schemaHelper.jsonToSchema(binding), key: binding.protocol() }) %>
<% } %>
<% } %>
\ No newline at end of file
diff --git a/src/components/Extensions.ejs b/src/components/Extensions.ejs
index 41c29fe..9a25262 100644
--- a/src/components/Extensions.ejs
+++ b/src/components/Extensions.ejs
@@ -1,5 +1,3 @@
-
-
<% if (Object.keys(schemaHelper.getCustomExtensions(extensions) || {}).length !== 0) { %>
- <%- include(SchemaPath,{ schemaName:name, schema: schemaHelper.jsonToSchema(extensions) }) %>
+ <%- include(schemaPath,{ schemaName:name, schema: schemaHelper.jsonToSchema(extensions) }) %>
<% } %>
\ No newline at end of file
diff --git a/src/components/Operations.ejs b/src/components/Operations.ejs
index e69de29..98fca70 100644
--- a/src/components/Operations.ejs
+++ b/src/components/Operations.ejs
@@ -0,0 +1,125 @@
+<% if(!channels.isEmpty()) { %>
+ Operations
+ <% for(let channel of channels.all()) { %>
+ <% for(let operation of channel.operations().all()) { %>
+ <% if(operation && channel) { %>
+ <% let type;
+ const applyToAllServers = allServersLength === channel.servers().all().length;
+ const servers = applyToAllServers ? [] : channel.servers().all();
+ const showInfoList = operation.operationId() || (servers && servers.length);
+ if (operation.isSend()) {
+ if (operation.reply() !== undefined) {
+ type = 'request';
+ } else {
+ type = 'send';
+ }
+ } else if (operation.isReceive()) {
+ if (operation.reply() !== undefined) {
+ type = 'reply';
+ } else {
+ type = 'receive';
+ }
+ } %>
+ <%- md.render(`${getRenderedTypeForOperation({type})} \`${channel.address()}\` Operation`) %>
+ <% if(operation.summary()) { %>
+ <%- md.render(`*${operation.summary().trim()}*`) %>
+ <% } %>
+ <% if(showInfoList) { %>
+
+ <% if(operation.operationId()) { %>
+ - Operation ID: <%= operation.operationId() %>
+ <% if(servers && servers.length) { %>
+ - Available Only on Server:
+ <%= servers.map(s => {
+ const serverId = s.id();
+ const slug = FormatHelpers.slugify(serverId);
+ return `[${serverId}](#${slug}-server)`;
+ }).join(', ') %>
+
+ <% } %>
+ <% } %>
+
+ <% } %>
+ <% if(channel.hasDescription()) { %>
+ <%- md.render(channel.description()) %>
+ <% } %>
+ <% if(operation.hasDescription()) { %>
+ <%- md.render(operation.description()) %>
+ <% } %>
+ <% if(operation.externalDocs()) { %>
+ <%= (operation.externalDocs().description() || 'Find more info here.') %>
+ <% } %>
+
+ <%- include(tagsPath,{ name:"Operation tags", tags: operation.tags() }) %>
+
+ <% const parameters = schemaHelper.parametersToSchema(channel.parameters().all()); %>
+ <% if(parameters) { %>
+ Parameters
+ <%- include(schemaPath,{}) %>
+ <% } %>
+
+ <%- include(securityPath,{ header:'Additional security requirements', protocol: null, security: operation.security(), serverHelper, md }) %>
+
+
+ <%- include(bindingsPath,{ name:"Channel specific information", bindings: channel.bindings(), schemaHelper, schemaPath }) %>
+ <%- include(bindingsPath,{ name:"Operation specific information", bindings: operation.bindings(), schemaHelper, schemaPath }) %>
+ <%- include(extensionsPath,{ name:"Channel extensions", extensions: channel.extensions(), schemaHelper, schemaPath }) %>
+ <%- include(extensionsPath,{ name:"Operation extensions", extensions: operation.extensions(), schemaHelper, schemaPath }) %>
+
+
+ <% const messages = operation.messages().all(); %>
+ <% if (messages.length !== 0) { %>
+ <% const messageText = getOperationMessageText({type}); %>
+ <% if(messages.length > 1) { %>
+ <%= messageText %>
+ <% } %>
+ <% for(let message of messages) { %>
+ <%- include(messagePath,{message}) %>
+ <% } %>
+ <% } %>
+
+ <% } %>
+ <% } %>
+ <% } %>
+<% } %>
+
+
+
+<% function getRenderedTypeForOperation({type}) {
+ if (isV3) {
+ switch (type) {
+ case 'request':
+ return 'REQUEST';
+ case 'send':
+ return 'SEND';
+ case 'reply':
+ return 'REPLY';
+ case 'receive':
+ return 'RECEIVE';
+ }
+ }
+ switch (type) {
+ case 'send':
+ return 'SUB';
+ case 'receive':
+ return 'PUB';
+ }
+
+ return 'UNKNOWN';
+ }
+
+ function getOperationMessageText({type}) {
+ let messagesText = 'Accepts **one of** the following messages:';
+ if (isV3) {
+ if (type === 'send') {
+ messagesText = 'Sending **one of** the following messages:';
+ } else if (type === 'request') {
+ messagesText = 'Request contains **one of** the following messages:';
+ } else if (type === 'receive') {
+ messagesText = 'Receive **one of** the following messages:';
+ } else if (type === 'reply') {
+ messagesText = 'Request contains **one of** the following messages:';
+ }
+ }
+ return messagesText;
+ } %>
\ No newline at end of file
diff --git a/src/components/Security.ejs b/src/components/Security.ejs
index e69de29..79ba76f 100644
--- a/src/components/Security.ejs
+++ b/src/components/Security.ejs
@@ -0,0 +1,117 @@
+<% let renderedRequirements; %>
+<% if(security?.length) { %>
+ <% renderedRequirements = security.map((requirement, idx) => {return { requirement, idx }}).filter(Boolean); %>
+<% }else if(protocol == 'kafka' || protocol == 'kafka-secure') { %>
+ <% renderedRequirements = {requirement: null}; %>
+<% } %>
+<% if(renderedRequirements) { %>
+ <% if(security?.length && renderedRequirements.length !== 0) { %>
+
+
<%= (header || "Security") %>
+ <% for(let { requirement, idx } of renderedRequirements) { %>
+ <% let renderedServerSecurities; %>
+ <% if(!requirement && (protocol == 'kafka' || protocol == 'kafka-secure')) { %>
+ <% renderedServerSecurities = {securitySchema: null}; %>
+ <% }else if(requirement) { %>
+ <% renderedServerSecurities = requirement.all().map(requirementItem =>{return {securitySchema: requirementItem.scheme(), requiredScopes: requirementItem.scopes(), key: requirementItem.scheme().type()}} ).filter(Boolean); %>
+ <% } %>
+ <% if(renderedServerSecurities) { %>
+ <% if(requirement && renderedServerSecurities.length !== 0) { %>
+
Security Requirement
+ <% for(let {securitySchema, requiredScopes, key} of renderedServerSecurities) {
+ let schemas = [];
+ renderSecuritySchemasBasic({ securitySchema, schemas });
+ renderSecuritySchemasKafka({ protocol, securitySchema, schemas });
+ renderSecuritySchemasFlows({ securitySchema, requiredScopes, schemas });
+ schemas.filter(Boolean);
+ const type = securitySchema?.type() && serverHelper.securityType(securitySchema.type()); %>
+
+ <% if(type) { %>
+ - Type: <%= type %>
+ <% } %>
+ <% if(schemas.length > 0) { %>
+ <% for(let schema of schemas) { %>
+ <% if(schema.split('_')[0] == 'OpenID') { %>
+ - OpenID Connect URL: <%= schema.split('_')[2] %>
+ <% }else if(schema.split('_')[0] == 'RequiredScopes'){ %>
+ - Required Scopes: <%= schema.split('_')[1]? schema.split('_')[1]: 'Nil' %>
+
+
+
+ Flow |
+ Auth URL |
+ Token URL |
+ Refresh URL |
+ Scopes |
+
+
+
+ <% for(let entry of JSON.parse(schema.split('_')[2])){ %>
+
+ <%= entry[0] %> |
+ <%= entry[1] %> |
+ <%= entry[2] %> |
+ <%= entry[3] %> |
+ <%= entry[4] %> |
+
+ <% } %>
+
+
+ <% }else { %>
+ - <%= schema %>
+ <% } %>
+ <% } %>
+ <% } %>
+
+ <% if(securitySchema?.hasDescription()) { %>
+ <%- md.render(securitySchema.description()) %>
+ <% } %>
+ <% } %>
+ <% } %>
+ <% } %>
+ <% } %>
+
+ <% } %>
+<% } %>
+
+<% function renderSecuritySchemasBasic({ securitySchema, schemas }) {
+ if (securitySchema) {
+ if (securitySchema.name()) {
+ schemas.push(`Name: ${securitySchema.name()}`);
+ }
+ if (securitySchema.in()) {
+ schemas.push(`In: ${securitySchema.in()}`);
+ }
+ if (securitySchema.scheme()) {
+ schemas.push(`Scheme: ${securitySchema.scheme()}`);
+ }
+ if (securitySchema.bearerFormat()) {
+ schemas.push(`Bearer format: ${securitySchema.bearerFormat()}`);
+ }
+ if (securitySchema.openIdConnectUrl()) {
+ schemas.push(`OpenID_${securitySchema.openIdConnectUrl()}_${securitySchema.openIdConnectUrl()}`);
+ }
+ }
+ }
+
+ function renderSecuritySchemasKafka({ protocol, securitySchema, schemas }) {
+ const isKafkaProtocol = protocol === 'kafka' || protocol === 'kafka-secure';
+ if (!isKafkaProtocol) { return; }
+
+ const { securityProtocol, saslMechanism } = serverHelper.getKafkaSecurity(protocol,securitySchema);
+
+ if (securityProtocol) { schemas.push(`security.protocol: ${securityProtocol}`); }
+ if (saslMechanism) { schemas.push(`sasl.mechanism: ${saslMechanism}`); }
+ }
+
+
+ function flowsRenderer([flowName, flow]) {
+ return [serverHelper.flowName(flowName) || '-', (flow?.authorizationUrl() ? flow.authorizationUrl() : '-'), (flow?.tokenUrl() ? flow.tokenUrl() : '-'), (flow?.refreshUrl() ? flow.refreshUrl() : '-'), Object.keys(flow?.scopes() || {}).length ? Object.keys(flow.scopes()).map(v => `\`${v}\``).join(', ') : '-' ];
+ }
+ function renderSecuritySchemasFlows({ securitySchema, requiredScopes, schemas }) {
+ const flows = securitySchema?.flows();
+ if (!flows) { return; }
+
+ const flowsData = Object.entries({authorizationCode: flows.authorizationCode(),clientCredentials: flows.clientCredentials(),implicit: flows.implicit(),password: flows.password()});
+ schemas.push( `RequiredScopes_${ requiredScopes.map(v => `\`${v}\``).join(', ')}_${JSON.stringify(flowsData.map(entry => flowsRenderer(entry)))}` );
+ } %>
diff --git a/src/components/Servers.ejs b/src/components/Servers.ejs
index 01d9c87..ed729bc 100644
--- a/src/components/Servers.ejs
+++ b/src/components/Servers.ejs
@@ -9,9 +9,7 @@
Protocol: <%= server.protocol() %> <%= server.protocolVersion() ? server.protocolVersion() : '' %>
<% if(server.hasDescription()) { %>
-
- Description: <%= server.description() %>
-
+ <%- md.render(`Description: ${server.description()}`) %>
<% } %>
URL Variables
@@ -44,11 +42,11 @@
- <%- include(securityPath,{ prototcol: server.protocol(), security: server.security() }) %>
+ <%- include(securityPath,{ header:null, protocol: server.protocol(), security: server.security(), serverHelper, md }) %>
<%- include(tagsPath,{ name:"Tags", tags: server.tags() }) %>
- <%- include(bindingsPath,{ name:"Server specific information", bindings: server.bindings(), schemaHelper }) %>
- <%- include(extensionsPath,{ name:"Server extensions", extensions: server.extensions(), schemaHelper }) %>
+ <%- include(bindingsPath,{ name:"Server specific information", bindings: server.bindings(), schemaHelper, schemaPath }) %>
+ <%- include(extensionsPath,{ name:"Server extensions", extensions: server.extensions(), schemaHelper, schemaPath }) %>
<% } %>