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(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' %>
  • + + + + + + + + + + + + <% for(let entry of JSON.parse(schema.split('_')[2])){ %> + + + + + + + + <% } %> + +
    FlowAuth URLToken URLRefresh URLScopes
    <%= 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 }) %> <% } %>