diff --git a/.gitignore b/.gitignore index 5f8d342..fa47056 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .vscode-test/ *.vsix .DS_Store +package-lock.json \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 86d202d..27a360c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,14 @@ "license": "Apache-2.0", "dependencies": { "@asyncapi/parser": "^3.0.7", + "@types/markdown-it": "^13.0.7", + "@types/turndown": "^5.0.4", "ejs": "^3.1.9", "markdown-it": "^14.0.0", "mermaid": "^10.8.0", - "@types/markdown-it": "^13.0.7", + "panzoom": "^9.4.3", + "turndown": "^7.2.0", + "yaml": "^2.4.2" }, "devDependencies": { "@asyncapi/react-component": "^1.2.7", @@ -309,6 +313,11 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -732,9 +741,9 @@ } }, "node_modules/@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "node_modules/@types/json-schema": { @@ -742,7 +751,6 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, - "node_modules/@types/linkify-it": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", @@ -757,11 +765,18 @@ "@types/mdurl": "*" } }, + "node_modules/@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/mdurl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==" - }, "node_modules/@types/minimatch": { "version": "5.1.2", @@ -800,6 +815,11 @@ "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", "dev": true }, + "node_modules/@types/turndown": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz", + "integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==" + }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -1393,6 +1413,14 @@ "ajv": "^8.8.2" } }, + "node_modules/amator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz", + "integrity": "sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==", + "dependencies": { + "bezier-easing": "^2.0.3" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1527,6 +1555,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + }, "node_modules/big-integer": { "version": "1.6.51", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", @@ -2064,7 +2097,6 @@ "node": ">=12" } }, - "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -2080,7 +2112,6 @@ "node": ">=12" } }, - "node_modules/d3-chord": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", @@ -2100,7 +2131,6 @@ "node": ">=12" } }, - "node_modules/d3-contour": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", @@ -2108,7 +2138,6 @@ "dependencies": { "d3-array": "^3.2.0" }, - "engines": { "node": ">=12" } @@ -3361,20 +3390,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -4892,7 +4907,6 @@ "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-util-decode-string": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", @@ -4913,7 +4927,6 @@ "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } - }, "node_modules/micromark-util-encode": { "version": "1.1.0", @@ -4944,7 +4957,6 @@ "url": "https://opencollective.com/unified" } ] - }, "node_modules/micromark-util-normalize-identifier": { "version": "1.1.0", @@ -5037,7 +5049,6 @@ "url": "https://opencollective.com/unified" } ] - }, "node_modules/micromark-util-types": { "version": "1.1.0", @@ -5297,6 +5308,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ngraph.events": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.2.tgz", + "integrity": "sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ==" + }, "node_modules/nimma": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.2.tgz", @@ -5487,6 +5503,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/panzoom": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz", + "integrity": "sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==", + "dependencies": { + "amator": "^1.1.0", + "ngraph.events": "^1.2.2", + "wheel": "^1.0.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6545,6 +6571,14 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7049,6 +7083,11 @@ "node": ">=10" } }, + "node_modules/wheel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz", + "integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7189,6 +7228,17 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", + "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index d30832e..476226b 100644 --- a/package.json +++ b/package.json @@ -55,8 +55,8 @@ "command": "asyncapi.markdown", "title": "Preview Asyncapi Markdown", "icon": { - "light": "resources/icons/open-preview_black.svg", - "dark": "resources/icons/open-preview_white.svg" + "light": "resources/icons/open-markdown-preview_black.svg", + "dark": "resources/icons/open-markdown-preview_white.svg" } } ], @@ -147,9 +147,13 @@ }, "dependencies": { "@asyncapi/parser": "^3.0.7", + "@types/markdown-it": "^13.0.7", + "@types/turndown": "^5.0.4", "ejs": "^3.1.9", "markdown-it": "^14.0.0", - "mermaid": "^10.8.0" - "@types/markdown-it": "^13.0.7", + "mermaid": "^10.8.0", + "panzoom": "^9.4.3", + "turndown": "^7.2.0", + "yaml": "^2.4.2" } } diff --git a/resources/icons/open-markdown-preview_black.svg b/resources/icons/open-markdown-preview_black.svg new file mode 100644 index 0000000..f8741fe --- /dev/null +++ b/resources/icons/open-markdown-preview_black.svg @@ -0,0 +1,17 @@ + +Created with Fabric.js 3.6.6 + + + + + + + + + + + + + M + + \ No newline at end of file diff --git a/resources/icons/open-markdown-preview_white.svg b/resources/icons/open-markdown-preview_white.svg new file mode 100644 index 0000000..f9daea5 --- /dev/null +++ b/resources/icons/open-markdown-preview_white.svg @@ -0,0 +1,17 @@ + +Created with Fabric.js 3.6.6 + + + + + + + + + + + + + M + + \ No newline at end of file diff --git a/src/Asyncapi.ts b/src/Asyncapi.ts index bacef3b..b9ee0c3 100644 --- a/src/Asyncapi.ts +++ b/src/Asyncapi.ts @@ -1,29 +1,18 @@ -import {AsyncAPIDocumentInterface, SchemaV2 as SchemaModel } from '@asyncapi/parser'; +import { AsyncAPIDocumentInterface, SchemaV2 as SchemaModel } from '@asyncapi/parser'; 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'; import { sample } from 'openapi-sampler'; 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', - 'integer', - 'boolean', - 'array', - 'object', - 'null', -]; - -const RESTRICTED_ANY = 'restricted any'; +const jsonSchemaTypes: string[] = ['string', 'number', 'integer', 'boolean', 'array', 'object', 'null']; + const NEVER = 'never'; const UNKNOWN = 'unknown'; const ANY = 'any'; @@ -55,15 +44,14 @@ const jsonSchemaKeywordTypes = { }; class SchemaHelper { - - static toSchemaType(schema: { json: () => boolean; isBooleanSchema: () => any; not: () => any; }):any { + static toSchemaType(schema: { json: () => boolean; isBooleanSchema: () => any; not: () => any }): any { if (!schema || typeof schema.json !== 'function') { return UNKNOWN; } if (schema.isBooleanSchema()) { if (schema.json() === true) { return ANY; - } + } return NEVER; } // handle case with `{}` schemas @@ -92,7 +80,10 @@ class SchemaHelper { return type; } - static toType(type: string, schema: { json?: () => boolean; isBooleanSchema?: () => any; not?: () => any; items?: any; additionalItems?: any; }) { + static toType( + type: string, + schema: { json?: () => boolean; isBooleanSchema?: () => any; not?: () => any; items?: any; additionalItems?: any } + ) { if (type === 'array') { const items = schema.items(); if (Array.isArray(items)) { @@ -115,7 +106,14 @@ class SchemaHelper { return type; } - static toCombinedType(schema: { json?: () => boolean; isBooleanSchema?: () => any; not?: () => any; oneOf?: any; anyOf?: any; allOf?: any; }) { + static toCombinedType(schema: { + json?: () => boolean; + isBooleanSchema?: () => any; + not?: () => any; + oneOf?: any; + anyOf?: any; + allOf?: any; + }) { const t = []; if (schema.oneOf()) { t.push('oneOf'); @@ -132,7 +130,17 @@ class SchemaHelper { return t[0]; } - static inferType(schema: { json: any; isBooleanSchema?: (() => any) | (() => any) | undefined; not?: (() => any) | (() => any) | undefined; type?: any; const?: any; enum?: any; oneOf?: any; anyOf?: any; allOf?: any; }) { + static inferType(schema: { + json: any; + isBooleanSchema?: (() => any); + not?: (() => any) ; + type?: any; + const?: any; + enum?: any; + oneOf?: any; + anyOf?: any; + allOf?: any; + }) { let types = schema.type(); if (types !== undefined) { @@ -156,20 +164,22 @@ class SchemaHelper { } const enumValue = schema.enum(); if (Array.isArray(enumValue) && enumValue.length) { - const inferredType = Array.from(new Set(enumValue.map(e => { - const typeOf = typeof e; - if (typeOf === 'number' && Number.isInteger(e)) { - return 'integer'; - } - return typeOf; - }))); + const inferredType = Array.from( + new Set( + enumValue.map(e => { + const typeOf = typeof e; + if (typeOf === 'number' && Number.isInteger(e)) { + return 'integer'; + } + return typeOf; + }) + ) + ); return inferredType.length === 1 ? inferredType[0] : inferredType; } const schemaKeys = Object.keys(schema.json() || {}) || []; - const hasInferredTypes = Object.keys(jsonSchemaKeywordTypes).some(key => - schemaKeys.includes(key), - ); + const hasInferredTypes = Object.keys(jsonSchemaKeywordTypes).some(key => schemaKeys.includes(key)); if (hasInferredTypes === true) { return ''; } @@ -179,7 +189,7 @@ class SchemaHelper { return ANY; } - static prettifyValue(value: { toString: () => any; }) { + static prettifyValue(value: { toString: () => any }) { const typeOf = typeof value; if (typeOf === 'string') { return `"${value}"`; @@ -193,7 +203,20 @@ class SchemaHelper { return JSON.stringify(value); } - static humanizeConstraints(schema: { minimum: () => undefined; exclusiveMinimum: () => undefined; maximum: () => undefined; exclusiveMaximum: () => undefined; multipleOf: () => { toString: (arg0: number) => any; } | undefined; minLength: () => number | undefined; maxLength: () => undefined; uniqueItems: () => any; minItems: () => number | undefined; maxItems: () => undefined; minProperties: () => number | undefined; maxProperties: () => undefined; }) { + static humanizeConstraints(schema: { + minimum: () => undefined; + exclusiveMinimum: () => undefined; + maximum: () => undefined; + exclusiveMaximum: () => undefined; + multipleOf: () => { toString: (arg0: number) => any } | undefined; + minLength: () => number | undefined; + maxLength: () => undefined; + uniqueItems: () => any; + minItems: () => number | undefined; + maxItems: () => undefined; + minProperties: () => number | undefined; + maxProperties: () => undefined; + }) { const constraints = []; // related to number/integer @@ -201,45 +224,31 @@ class SchemaHelper { schema.minimum(), schema.exclusiveMinimum(), schema.maximum(), - schema.exclusiveMaximum(), + schema.exclusiveMaximum() ); if (numberRange !== undefined) { constraints.push(numberRange); } - const multipleOfConstraint = this.humanizeMultipleOfConstraint( - schema.multipleOf(), - ); + const multipleOfConstraint = this.humanizeMultipleOfConstraint(schema.multipleOf()); if (multipleOfConstraint !== undefined) { constraints.push(multipleOfConstraint); } // related to string - const stringRange = this.humanizeRangeConstraint( - 'characters', - schema.minLength(), - schema.maxLength(), - ); + const stringRange = this.humanizeRangeConstraint('characters', schema.minLength(), schema.maxLength()); if (stringRange !== undefined) { constraints.push(stringRange); } // related to array const hasUniqueItems = schema.uniqueItems(); - const arrayRange = this.humanizeRangeConstraint( - hasUniqueItems ? 'unique items' : 'items', - schema.minItems(), - schema.maxItems(), - ); + const arrayRange = this.humanizeRangeConstraint(hasUniqueItems ? 'unique items' : 'items', schema.minItems(), schema.maxItems()); if (arrayRange !== undefined) { constraints.push(arrayRange); } // related to object - const objectRange = this.humanizeRangeConstraint( - 'properties', - schema.minProperties(), - schema.maxProperties(), - ); + const objectRange = this.humanizeRangeConstraint('properties', schema.minProperties(), schema.maxProperties()); if (objectRange !== undefined) { constraints.push(objectRange); } @@ -247,12 +256,7 @@ class SchemaHelper { return constraints; } - static humanizeNumberRangeConstraint( - min: undefined, - exclusiveMin: undefined, - max: undefined, - exclusiveMax: undefined, - ) { + static humanizeNumberRangeConstraint(min: any, exclusiveMin: any, max: any, exclusiveMax: any) { const hasExclusiveMin = exclusiveMin !== undefined; const hasMin = min !== undefined || hasExclusiveMin; const hasExclusiveMax = exclusiveMax !== undefined; @@ -275,24 +279,18 @@ class SchemaHelper { return numberRange; } - static humanizeMultipleOfConstraint( - multipleOf: { toString: (arg0: number) => any; } | undefined, - ) { + static humanizeMultipleOfConstraint(multipleOf: { toString: (arg0: number) => any } | undefined) { if (multipleOf === undefined) { return; } const strigifiedMultipleOf = multipleOf.toString(10); - if (!(/^0\.0*1$/).test(strigifiedMultipleOf)) { + if (!/^0\.0*1$/.test(strigifiedMultipleOf)) { return `multiple of ${strigifiedMultipleOf}`; } - return `decimal places <= ${strigifiedMultipleOf.split('.')[1].length}`; + return `decimal places <= ${strigifiedMultipleOf?.split('.')[1].length}`; } - static humanizeRangeConstraint( - description: any, - min: number | undefined, - max: undefined, - ) { + static humanizeRangeConstraint(description: any, min: number | undefined, max: undefined) { let stringRange; if (min !== undefined && max !== undefined) { if (min === max) { @@ -312,14 +310,14 @@ class SchemaHelper { return stringRange; } - static getDependentRequired(propertyName: string, schema: { dependencies: () => any; }) { + static getDependentRequired(propertyName: string, schema: { dependencies: () => any }) { const dependentRequired = []; const dependencies = schema.dependencies(); if (!dependencies) { return; } - for (const [prop, array] of Object.entries(dependencies)) { + for (const [prop, array] of Object.entries(dependencies || {})) { if (Array.isArray(array) && array.includes(propertyName)) { dependentRequired.push(prop); } @@ -327,14 +325,14 @@ class SchemaHelper { return dependentRequired.length ? dependentRequired : undefined; } - static getDependentSchemas(schema: { dependencies: () => any; }) { + static getDependentSchemas(schema: { dependencies: () => any }) { const dependencies = schema.dependencies(); if (!dependencies) { return; } - const records:any = {}; - for (const [prop, propSchema] of Object.entries(dependencies)) { + const records: any = {}; + for (const [prop, propSchema] of Object.entries(dependencies || {})) { if (typeof propSchema === 'object' && !Array.isArray(propSchema)) { records[String(prop)] = propSchema; } @@ -343,39 +341,32 @@ class SchemaHelper { return undefined; } - const json:object = { + const json: object = { type: 'object', - properties: Object.entries(records).reduce( - (obj:any, [propertyName, propertySchema]:any[]) => { - obj[String(propertyName)] = Object.assign({}, propertySchema.json()); - return obj; - }, - {}, - ), + properties: Object.entries(records || {}).reduce((obj: any, [propertyName, propertySchema]: any[]) => { + obj[String(propertyName)] = Object.assign({}, propertySchema.json()); + return obj; + }, {}), [extRenderType]: false, [extRenderAdditionalInfo]: false, }; return new SchemaModel(json); } - + static parametersToSchema(parameters: any[]) { if (parameters.length === 0) { return; } - const json:object = { + 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; - }, - {}, - ), + 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, @@ -392,8 +383,7 @@ class SchemaHelper { }; } if (typeof value !== 'object') { - const str = - typeof value.toString === 'function' ? value.toString() : value; + const str = typeof value.toString === 'function' ? value.toString() : value; return { type: 'string', const: str, @@ -414,7 +404,7 @@ class SchemaHelper { } return { type: 'object', - properties: Object.entries(value).reduce((obj:any, [k, v]) => { + properties: Object.entries(value || {}).reduce((obj: any, [k, v]) => { obj[String(k)] = this.jsonFieldToSchema(v); return obj; }, {}), @@ -435,9 +425,7 @@ class SchemaHelper { if ( value && typeof value === 'object' && - (jsonSchemaTypes.includes(value.type) || - (Array.isArray(value.type) && - value.type.some((t: string) => !jsonSchemaTypes.includes(t)))) + (jsonSchemaTypes.includes(value.type) || (Array.isArray(value.type) && value.type.some((t: string) => !jsonSchemaTypes.includes(t)))) ) { return true; } @@ -447,12 +435,9 @@ class SchemaHelper { static getCustomExtensions(item: any): Record { try { const extensions = item.extensions().all(); - return extensions.reduce((acc: { [x: string]: any; }, ext: { id: () => any; value: () => any; }) => { + return extensions.reduce((acc: { [x: string]: any }, ext: { id: () => any; value: () => any }) => { const extName = ext.id(); - if ( - !extName.startsWith('x-parser-') && - !extName.startsWith('x-schema-private-') - ) { + if (!extName.startsWith('x-parser-') && !extName.startsWith('x-schema-private-')) { acc[String(extName)] = ext.value(); } return acc; @@ -466,53 +451,53 @@ 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'; + 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'; + 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; }) { + static getKafkaSecurity(protocol: string, securitySchema: { type: () => any }) { let securityProtocol; let saslMechanism; if (protocol === 'kafka') { @@ -528,24 +513,24 @@ class ServerHelper { } 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; + 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; } } @@ -554,12 +539,14 @@ class ServerHelper { } class MessageHelper { - static getPayloadExamples(message: { examples: () => { (): any; new(): any; all: { (): any; new(): any; }; }; payload: () => any; }) { + static getPayloadExamples(message: { examples: () => { (): any; new (): any; all: { (): any; new (): any } }; payload: () => any }) { const examples = message.examples().all(); if (Array.isArray(examples) && examples.some(e => e.payload())) { const messageExamples = examples .map(e => { - if (!e.payload()) {return;} + if (!e.payload()) { + return; + } return { name: e.name(), summary: e.summary(), @@ -567,24 +554,26 @@ class MessageHelper { }; }) .filter(Boolean); - + if (messageExamples.length > 0) { return messageExamples; } } - + const payload = message.payload(); if (payload?.examples()) { return payload.examples().map((example: any) => ({ example })); } } - - static getHeadersExamples(message: { examples: () => { (): any; new(): any; all: { (): any; new(): any; }; }; headers: () => any; }) { + + static getHeadersExamples(message: { examples: () => { (): any; new (): any; all: { (): any; new (): any } }; headers: () => any }) { const examples = message.examples().all(); if (Array.isArray(examples) && examples.some(e => e.headers())) { const messageExamples = examples .map(e => { - if (!e.headers()) {return;} + if (!e.headers()) { + return; + } return { name: e.name(), summary: e.summary(), @@ -592,12 +581,12 @@ class MessageHelper { }; }) .filter(Boolean); - + if (messageExamples.length > 0) { return messageExamples; } } - + const headers = message.headers(); if (headers?.examples()) { return headers.examples().map((example: any) => ({ example })); @@ -609,53 +598,816 @@ class MessageHelper { } } -export default async function info(asyncapi:AsyncAPIDocumentInterface, context: vscode.ExtensionContext) { - - - const info = asyncapi.info(); - const templatePath = path.join(context.extensionPath,'dist', 'components','Asyncapi.ejs'); +function addServers(asyncapi: any) { + return { + isEmpty: () => (asyncapi.servers ? false : true), + all: () => { + return Object.entries(asyncapi.servers || {}).map((server: any) => { + let finalServerObject:any = null; + if (server[1]?.$ref) { + const parsedStr = server[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalServerObject = (asyncapi.components?.servers)? [server[0],asyncapi.components.servers[parsedStr[3]]] : server; + } else { + finalServerObject = server; + } + return { + id: () => finalServerObject[0], + url: () => (finalServerObject[1] && finalServerObject[1].host ? finalServerObject[1].host : finalServerObject[1]?.url), + protocol: () => (finalServerObject[1] && finalServerObject[1].protocol ? finalServerObject[1].protocol : ''), + protocolVersion: () => (finalServerObject[1] && finalServerObject[1].protocolVersion ? finalServerObject[1].protocolVersion : ''), + hasDescription: () => (finalServerObject[1] && finalServerObject[1].description ? true : false), + description: () => (finalServerObject[1] ? finalServerObject[1].description : ''), + summary: () => (finalServerObject[1] ? finalServerObject[1].summary : ''), + variables: () => + finalServerObject[1] && finalServerObject[1].variables + ? { + all: () => + Object.entries(finalServerObject[1].variables || {}).map((entry: any) => { + let finalVariablesObject = null; + if (entry[1]?.$ref) { + const parsedStr = entry[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalVariablesObject = (asyncapi.components?.serverVariables)? [entry[0], asyncapi.components.serverVariables[parsedStr[3]]] : entry; + } else { + finalVariablesObject = entry; + } + return { + id: () => (entry[0] ? entry[0] : ''), + description: () => (entry[1] && entry[1].description ? entry[1].description : ''), + hasDefaultValue: () => (entry[1] && entry[1].default ? true : false), + defaultValue: () => (entry[1] && entry[1].default ? entry[1].default : ''), + hasAllowedValues: () => (entry[1] && entry[1].enum ? true : false), + allowedValues: () => (entry[1] && entry[1].enum ? entry[1].enum : []), + }; + }), + } + : '', + security: () => { + return finalServerObject[1].security?.map((sec: any) => { + return { + all: () => + Object.entries(sec || {}).map((security: any) => { + return { + scheme: () => { + return { + type: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] + ? asyncapi.components.securitySchemes[security[1].split('/').pop()].type + : '', + hasDescription: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] && + asyncapi.components.securitySchemes[security[1].split('/').pop()].description + ? true + : false, + description: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].description, + name: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].name, + in: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].in, + bearerFormat: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].bearerFormat, + openIdConnectUrl: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].openIdConnectUrl, + scheme: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].scheme, + flows: () => { + if (!asyncapi.components.securitySchemes[security[1].split('/').pop()].flows) { + return; + } + return { + authorizationCode: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows?.authorizationCode + .authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows?.authorizationCode?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows?.authorizationCode?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows?.authorizationCode + ?.availableScopes || [], + }; + }, + clientCredentials: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.clientCredentials + ?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.clientCredentials?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.clientCredentials?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.clientCredentials + ?.availableScopes || [], + }; + }, + implicit: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit?.availableScopes || + [], + }; + }, + password: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password?.availableScopes || + [], + }; + }, + }; + }, + }; + }, + scopes: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] + ? asyncapi.components.securitySchemes[security[1].split('/').pop()].scopes || [] + : '', + }; + }), + }; + }); + }, + extensions: () => { + return { + all: () => + Object.entries(finalServerObject[1].extensions || {}).map((extension: any) => { + return { + id: () => extension[1]?.id, + value: () => extension[1]?.value, + }; + }), + }; + }, + bindings: () => { + return { + isEmpty: () => (finalServerObject[1].bindings ? false : true), + all: () => { + return Object.entries(finalServerObject[1].bindings || {}).map((binding: any) => { + let finalBindingsObject:any = null; + if (binding[1]?.$ref) { + const parsedStr = binding[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalBindingsObject = (asyncapi?.components?.serverBindings)? [binding[0], asyncapi.components.serverBindings[parsedStr[3]]] : binding; + } else { + finalBindingsObject = binding; + } + return { + protocol: () => (finalBindingsObject[1] && finalBindingsObject[1].protocol ? finalBindingsObject[1].protocol : ''), + json: () => (finalBindingsObject[1] && finalBindingsObject[1].json ? finalBindingsObject[1].json : ''), + type: () => (finalBindingsObject[1] && finalBindingsObject[1].type ? finalBindingsObject[1].type : ''), + }; + }); + }, + }; + }, + tags: () => { + return { + isEmpty: () => (finalServerObject[1].tags ? false : true), + all: () => + Object.entries(finalServerObject[1].tags || {}).map((tag: any) => { + return { + name: () => (tag[1] && tag[1].name ? tag[1].name : ''), + description: () => (tag[1] && tag[1].description ? tag[1].description : ''), + externalDocs: () => { + let finalexternalDocsObject:any = null; + if (tag[1]?.externalDocs?.$ref) { + const parsedStr = tag[1].externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : tag[1]; + + } else { + finalexternalDocsObject = tag[1]; + } + return { + url: () => finalexternalDocsObject?.externalDocs?.url, + description: () => finalexternalDocsObject?.externalDocs?.description, + hasDescription: () => (finalexternalDocsObject?.externalDocs?.description ? true : false), + }; + }, + }; + }), + }; + }, + }; + }); + }, + }; +} - return await ejs.renderFile(templatePath, { +export default async function asyncapiMarkdown(asyncapi: any, context: vscode.ExtensionContext) { + try{ + if(!asyncapi || !context) {return;} + const templatePath = path.join(context.extensionPath, 'dist', 'components', 'Asyncapi.ejs'); + if (!asyncapi.isAsyncapiParser) { + return await ejs.renderFile(templatePath, { + info: { + title: asyncapi.info && asyncapi.info.title ? asyncapi.info.title : '', + version: asyncapi.info && asyncapi.info.version ? asyncapi.info.version : '', + defaultContentType: asyncapi.defaultContentType ? asyncapi.defaultContentType : '', + specId: asyncapi.info && asyncapi.info.specId ? asyncapi.info.specId : '', + termsOfService: asyncapi.info && asyncapi.info.termsOfService ? asyncapi.info.termsOfService : '', + license: + asyncapi.info && asyncapi.info.license ? { url: () => asyncapi.info.license.url, name: () => asyncapi.info.license.name } : null, + contact: + asyncapi.info && asyncapi.info.contact + ? { name: () => asyncapi.info.contact.name, url: () => asyncapi.info.contact.url, email:()=> asyncapi.info.contact.email } + : null, + externalDocs: + asyncapi.info && asyncapi.info.externalDocs + ? { url: () => asyncapi.info.externalDocs.url, description: () => (asyncapi.info.externalDocs.description || asyncapi.info.externalDocs.$ref) } + : null, + hasDescription: asyncapi.info && asyncapi.info.description ? true : false, + description: asyncapi.info ? md.render(asyncapi.info.description || '') : '', + tags: { + isEmpty: () => (asyncapi.info.tags ? false : true), + all: () => + Object.entries(asyncapi.info.tags || {}).map((tag: any) => { + return { + name: () => (tag[1] && tag[1].name ? tag[1].name : ''), + description: () => (tag[1] && tag[1].description ? tag[1].description : ''), + externalDocs: () => { + let finalexternalDocsObject:any = null; + if (tag[1]?.externalDocs?.$ref) { + const parsedStr = tag[1].externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : tag[1]; + } else { + finalexternalDocsObject = tag[1]; + } + return { + url: () => finalexternalDocsObject?.externalDocs?.url, + description: () => finalexternalDocsObject?.externalDocs?.description, + hasDescription: () => (finalexternalDocsObject?.externalDocs?.description ? true : false), + }; + } + }; + }), + }, + }, + servers: { + servers: addServers(asyncapi), + schemaHelper: SchemaHelper, + serverHelper: ServerHelper, + md, + }, + operations: { + channels: { + isEmpty: () => (asyncapi.channels ? false : true), + all: () => + Object.entries(asyncapi.channels || {}).map((channel: any) => { + return { + servers: () => addServers(asyncapi), + operations: () => { + return { + all: () => + Object.entries(asyncapi.operations || { + [channel[1]?.subscribe?.operationId || "subscribe"]: { + ...channel[1]?.subscribe, + "channel": {"$ref": channel[1]?.subscribe? `#/channels/${channel[0]}`: null}, + "action": 'send', + "messages": (!channel[1]?.subscribe?.message?.oneOf)? { + "subscribeMessage": channel[1]?.subscribe?.message + } : channel[1]?.subscribe?.message?.oneOf, + "address": channel[0] + }, + [channel[1]?.publish?.operationId || "publish"]: { + ...channel[1]?.publish, + "channel": {"$ref": channel[1]?.publish? `#/channels/${channel[0]}`: null}, + "action": 'receive', + "messages": (!channel[1]?.publish?.message?.oneOf)? { + "publishMessage": channel[1]?.publish?.message + } : channel[1]?.publish?.message?.oneOf + }}) + .filter( + (operation: any) => operation[1].channel?.$ref?.split('channels/').pop().replaceAll('~1', '/') === channel[0] + ) + .map((operation: any) => { + return { + operationId: () => (operation[0] ? operation[0] : ''), + isSend: () => (operation[1] && operation[1].action === 'send' ? true : false), + isReceive: () => (operation[1] && operation[1].action === 'receive' ? true : false), + reply: () => (operation[1] && operation[1].reply ? operation[1].reply : false), + summary: () => (operation[1] && operation[1].summary ? operation[1].summary : ''), + hasDescription: () => (operation[1] && operation[1].description ? true : false), + description: () => (operation[1] && operation[1].description ? operation[1].description : ''), + externalDocs: () =>{ + let finalexternalDocsObject:any = null; + if (operation[1]?.externalDocs?.$ref) { + const parsedStr = operation[1].externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : operation[1]; + } else { + finalexternalDocsObject = operation[1]; + } + return { + url: () => finalexternalDocsObject?.externalDocs?.url, + description: () => finalexternalDocsObject?.externalDocs?.description, + hasDescription: () => (finalexternalDocsObject?.externalDocs?.description ? true : false), + }; + }, + tags: () => { + return { + isEmpty: () => (operation[1].tags ? false : true), + all: () => + Object.entries(operation[1].tags || {}).map((tag: any) => { + return { + name: () => (tag[1] && tag[1].name ? tag[1].name : ''), + description: () => (tag[1] && tag[1].description ? tag[1].description : ''), + externalDocs: () =>{ + let finalexternalDocsObject:any = null; + if (tag[1]?.externalDocs?.$ref) { + const parsedStr = tag[1].externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi?.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : tag[1]; + } else { + finalexternalDocsObject = tag[1]; + } + return { + url: () => finalexternalDocsObject?.externalDocs?.url, + description: () => finalexternalDocsObject?.externalDocs?.description, + hasDescription: () => (finalexternalDocsObject?.externalDocs?.description ? true : false), + }; + }, + }; + }), + }; + }, + extensions: () => { + return { + all: () => + Object.values(operation[1]?.extensions).map((extension: any) => { + return { + id: () => extension?.id, + value: () => extension?.value, + }; + }), + }; + }, + bindings: () => { + return { + isEmpty: () => (operation[1]?.bindings ? false : true), + all: () => { + return Object.entries(operation[1]?.bindings || {}).map((binding: any) => { + let finalBindingsObject:any = null; + if (binding[1]?.$ref) { + const parsedStr = binding[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalBindingsObject = (asyncapi?.components?.operationBindings)? [binding[0], asyncapi.components.operationBindings[parsedStr[3]]] : binding; + } else { + finalBindingsObject = binding; + } + return { + protocol: () => (finalBindingsObject[1] && finalBindingsObject[1].protocol ? finalBindingsObject[1].protocol : ''), + json: () => (finalBindingsObject[1] && finalBindingsObject[1].json ? finalBindingsObject[1].json : ''), + type: () => (finalBindingsObject[1] && finalBindingsObject[1].type ? finalBindingsObject[1].type : ''), + }; + }); + }, + }; + }, + security: () => { + return operation[1]?.security?.map((sec: any) => { + return { + all: () => + Object.entries(sec || {}).map((security: any) => { + return { + scheme: () => { + return { + type: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] + ? asyncapi.components.securitySchemes[security[1].split('/').pop()].type + : '', + hasDescription: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] && + asyncapi.components.securitySchemes[security[1].split('/').pop()].description + ? true + : false, + description: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].description, + name: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].name, + in: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].in, + bearerFormat: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].bearerFormat, + openIdConnectUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].openIdConnectUrl, + scheme: () => asyncapi.components.securitySchemes[security[1].split('/').pop()].scheme, + flows: () => { + if (!asyncapi.components.securitySchemes[security[1].split('/').pop()].flows) { + return; + } + return { + authorizationCode: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows + ?.authorizationCode.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows + ?.authorizationCode?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows + ?.authorizationCode?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()].flows + ?.authorizationCode?.availableScopes || [], + }; + }, + clientCredentials: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows + ?.clientCredentials?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows + ?.clientCredentials?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows + ?.clientCredentials?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows + ?.clientCredentials?.availableScopes || [], + }; + }, + implicit: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit + ?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit + ?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit + ?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.implicit + ?.availableScopes || [], + }; + }, + password: () => { + return { + authorizationUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password + ?.authorizationUrl, + refreshUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password + ?.refreshUrl, + tokenUrl: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password + ?.tokenUrl, + scopes: () => + asyncapi.components.securitySchemes[security[1].split('/').pop()]?.flows?.password + ?.availableScopes || [], + }; + }, + }; + }, + }; + }, + scopes: () => + asyncapi.components && + asyncapi.components.securitySchemes && + asyncapi.components.securitySchemes[security[1].split('/').pop()] + ? asyncapi.components.securitySchemes[security[1].split('/').pop()].scopes || [] + : '', + }; + }), + }; + }); + }, + messages: () => { + return { + all: () => { + let tmp: any = Object.values(operation[1].messages || {}); + return tmp.map((value:any)=>{ + let finalMessageObject:any = null; + if (value?.$ref) { + const parsedStr = value.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + if(parsedStr[1] === "channels"){ + const messageInstance = (asyncapi.channels[parsedStr[2]]?.messages)? asyncapi.channels[parsedStr[2]]?.messages[parsedStr[4]] : null; + finalMessageObject = messageInstance?.$ref? asyncapi?.components?.messages[messageInstance?.$ref?.split("/").pop()] : messageInstance; + }else{ + finalMessageObject = (asyncapi?.components?.messages)? asyncapi.components.messages[parsedStr[3]] : null; + } + } else { + finalMessageObject = value; + } + return { + id: () => finalMessageObject?.id, + title: () => finalMessageObject?.title, + name: () => finalMessageObject?.name, + hasDescription: () => (finalMessageObject?.description ? true : false), + description: () => finalMessageObject?.description, + contentType: () => finalMessageObject?.contentType, + summary: () => finalMessageObject?.summary, + correlationId: () => { + let finalcorrelationIdObject:any = null; + if (finalMessageObject?.correlationId?.$ref) { + const parsedStr = finalMessageObject.correlationId.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalcorrelationIdObject = (asyncapi?.components?.correlationIds)? asyncapi.components.correlationIds[parsedStr[3]] : finalMessageObject?.correlationId; + } else { + finalcorrelationIdObject = finalMessageObject?.correlationId; + } + return { + location: () => finalcorrelationIdObject?.location, + hasDescription: () => (finalcorrelationIdObject?.description ? true : false), + description: () => finalcorrelationIdObject?.description, + }; + }, + externalDocs: () => { + let finalexternalDocsObject:any = null; + if (finalMessageObject?.externalDocs?.$ref) { + const parsedStr = finalMessageObject.externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi?.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : finalMessageObject?.externalDocs; + } else { + finalexternalDocsObject = finalMessageObject?.externalDocs; + } + return { + url: () => finalexternalDocsObject?.url, + description: () => finalexternalDocsObject?.description, + hasDescription: () => (finalexternalDocsObject?.description ? true : false), + }; + }, + headers: () => { + if(finalMessageObject?.headers?.$ref){ + const parsedStr = finalMessageObject.headers.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + let finalHeadersObject = (asyncapi?.components?.schemas)? asyncapi.components.schemas[parsedStr[3]] : finalMessageObject?.headers; + return { + incorrect: true, + ...finalHeadersObject, + }; + }else if(finalMessageObject?.headers?.schemaFormat){ + return { + incorrect: true, + schemaFormat: finalMessageObject.headers.schemaFormat, + ...finalMessageObject?.headers?.schemaFormat?.schema, + }; + }else{ + return { + incorrect: true, + ...finalMessageObject?.headers, + }; + } + }, + payload: () => { + if(finalMessageObject?.payload?.$ref){ + const parsedStr = finalMessageObject.payload.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + let finalPayloadObject = (asyncapi?.components?.schemas)? asyncapi.components.schemas[parsedStr[3]] : finalMessageObject.payload; + return { + incorrect: true, + ...finalPayloadObject, + }; + }else if(finalMessageObject?.payload?.schemaFormat){ + return { + incorrect: true, + schemaFormat: finalMessageObject.payload.schemaFormat, + ...finalMessageObject?.payload?.schemaFormat?.schema, + }; + }else{ + return { + incorrect: true, + ...finalMessageObject?.payload, + }; + } + }, + tags: () => { + return { + isEmpty: () => (finalMessageObject?.tags ? false : true), + all: () => + Object.entries(finalMessageObject?.tags || {}).map((tag: any) => { + return { + name: () => (tag[1] && tag[1].name ? tag[1].name : ''), + description: () => (tag[1] && tag[1].description ? tag[1].description : ''), + externalDocs: () =>{ + let finalexternalDocsObject:any = null; + if (tag[1]?.externalDocs?.$ref) { + const parsedStr = tag[1].externalDocs.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalexternalDocsObject = (asyncapi?.components?.externalDocs)? asyncapi.components.externalDocs[parsedStr[3]] : tag[1]; + } else { + finalexternalDocsObject = tag[1]; + } + return { + url: () => finalexternalDocsObject?.externalDocs?.url, + description: () => finalexternalDocsObject?.externalDocs?.description, + hasDescription: () => (finalexternalDocsObject?.externalDocs?.description ? true : false), + }; + }, + }; + }), + }; + }, + extensions: () => { + return { + all: () => + Object.entries(finalMessageObject?.extensions || {}).map((extension: any) => { + return { + id: () => extension[1]?.id, + value: () => extension[1]?.value, + }; + }), + }; + }, + bindings: () => { + return { + isEmpty: () => (finalMessageObject?.bindings ? false : true), + all: () => { + return Object.entries(finalMessageObject?.bindings || {}).map((binding: any) => { + let finalBindingsObject:any = null; + if (binding[1]?.$ref) { + const parsedStr = binding[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalBindingsObject = (asyncapi?.components?.messageBindings)? [binding[0], asyncapi.components.messageBindings[parsedStr[3]]] : binding; + } else { + finalBindingsObject = binding; + } + return { + protocol: () => (finalBindingsObject[1] && finalBindingsObject[1].protocol ? finalBindingsObject[1].protocol : ''), + json: () => (finalBindingsObject[1] && finalBindingsObject[1].json ? finalBindingsObject[1].json : ''), + type: () => (finalBindingsObject[1] && finalBindingsObject[1].type ? finalBindingsObject[1].type : ''), + }; + }); + }, + }; + }, + traits: ()=>{ + let finalTraitsObject: any = []; + if(Array.isArray(finalMessageObject?.traits)){ + finalMessageObject.traits.map((trait: any)=>{ + if (trait?.$ref) { + const parsedStr = trait.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalTraitsObject.push((asyncapi?.components?.messageTraits)? asyncapi.components.messageTraits[parsedStr[3]] : trait); + } else { + finalTraitsObject.push(trait); + } + }); + } + return {incorrect: true, ...finalTraitsObject}; + } + }; + + }); + }, + }; + }, + traits: ()=>{ + let finalTraitsObject: any = []; + if(Array.isArray(operation[1]?.traits)) { + operation[1].traits.map((trait: any)=>{ + if (trait?.$ref) { + const parsedStr = trait.$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalTraitsObject.push((asyncapi?.components?.messageTraits)? asyncapi.components.operationTraits[parsedStr[3]] : trait); + } else { + finalTraitsObject.push(trait); + } + }); + } + return {incorrect: true, ...finalTraitsObject}; + } + }; + }), + }; + }, + address: () => (channel[1]?.address ? channel[1].address : channel[0]), + hasDescription: () => (channel[1] && channel[1].description ? true : false), + description: () => (channel[1] && channel[1].description ? channel[1].description : ''), + parameters: () => { + return { + all: () => + Object.entries(channel[1]?.parameters || {}).map((parameter: any) => { + let finalParametersObject:any = null; + if (parameter[1]?.$ref) { + const parsedStr = parameter[1].$ref.split('/').map((pathComponent: any)=> pathComponent?.replaceAll("~1","/")); + finalParametersObject = (asyncapi.components?.parameters)? [parameter[0], asyncapi.components.parameters[parsedStr[3]]] : parameter; + } else { + finalParametersObject = parameter; + } + return { + id: () => finalParametersObject[0], + schema: () => { + return { + json: () => { + return { + type: finalParametersObject[1]?.schema?.type, + title: finalParametersObject[1]?.schema?.title, + required: finalParametersObject[1]?.schema?.required, + }; + }, + }; + }, + description: () => finalParametersObject[1]?.description, + location: () => finalParametersObject[1]?.location, + }; + }), + }; + }, + extensions: () => { + return { + all: () => + Object.entries(channel[1]?.extensions || {}).map((extension: any) => { + return { + id: () => extension[1]?.id, + value: () => extension[1]?.value, + }; + }), + }; + }, + bindings: () => { + return { + isEmpty: () => (channel[1]?.bindings ? false : true), + all: () => { + return Object.entries(channel[1]?.bindings || {}).map((binding: any) => { + return { + protocol: () => (binding[1] && binding[1].protocol ? binding[1].protocol : ''), + json: () => (binding[1] && binding[1].json ? binding[1].json : ''), + type: () => (binding[1] && binding[1].type ? binding[1].type : ''), + }; + }); + }, + }; + }, + }; + }), + }, + isV3: asyncapi.asyncapi ? asyncapi.asyncapi.split('.')[0] === '3' : true, + schemaHelper: SchemaHelper, + serverHelper: ServerHelper, + messageHelper: MessageHelper, + allServersLength: asyncapi.servers ? Object.keys(asyncapi.servers).length : 0, + md, + }, + path: { + infoPath: path.join(context.extensionPath, 'dist', 'components', 'Info.ejs'), + tagsPath: path.join(context.extensionPath, 'dist', 'components', 'Tags.ejs'), + serversPath: path.join(context.extensionPath, 'dist', 'components', 'Servers.ejs'), + 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'), + operationsPath: path.join(context.extensionPath, 'dist', 'components', 'Operations.ejs'), + messagePath: path.join(context.extensionPath, 'dist', 'components', 'Message.ejs'), + }, + }); + } else { + const info = asyncapi.info(); + return await ejs.renderFile(templatePath, { info: { - title: info.title(), - version: info.version(), - defaultContentType: asyncapi.defaultContentType(), - specId: info.id(), - termsOfService: info.termsOfService(), - license: info.license(), - contact: info.contact(), - externalDocs: info.externalDocs(), - extensions: info.extensions(), - hasDescription: info.hasDescription(), - description: md.render(info.description() || ""), - tags: info.tags(), - + title: info.title(), + version: info.version(), + defaultContentType: asyncapi.defaultContentType(), + specId: info.id(), + termsOfService: info.termsOfService(), + license: info.license(), + contact: info.contact(), + externalDocs: info.externalDocs(), + extensions: info.extensions(), + hasDescription: info.hasDescription(), + description: md.render(info.description() || ''), + tags: info.tags(), }, - servers:{ - servers: asyncapi.servers(), - schemaHelper: SchemaHelper, - serverHelper: ServerHelper, - md + servers: { + servers: asyncapi.servers(), + schemaHelper: SchemaHelper, + serverHelper: ServerHelper, + md, }, - operations:{ + operations: { channels: asyncapi.channels(), isV3: asyncapi.version().split('.')[0] === '3', schemaHelper: SchemaHelper, serverHelper: ServerHelper, messageHelper: MessageHelper, allServersLength: asyncapi.servers().all().length, - md + md, }, - path:{ - infoPath: path.join(context.extensionPath,'dist', 'components','Info.ejs'), - tagsPath: path.join(context.extensionPath,'dist', 'components','Tags.ejs'), - serversPath: path.join(context.extensionPath,'dist', 'components','Servers.ejs'), - 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'), - operationsPath: path.join(context.extensionPath,'dist', 'components','Operations.ejs'), - messagePath: path.join(context.extensionPath,'dist', 'components','Message.ejs') - } - }); -} \ No newline at end of file + path: { + infoPath: path.join(context.extensionPath, 'dist', 'components', 'Info.ejs'), + tagsPath: path.join(context.extensionPath, 'dist', 'components', 'Tags.ejs'), + serversPath: path.join(context.extensionPath, 'dist', 'components', 'Servers.ejs'), + 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'), + operationsPath: path.join(context.extensionPath, 'dist', 'components', 'Operations.ejs'), + messagePath: path.join(context.extensionPath, 'dist', 'components', 'Message.ejs'), + }, + }); + } + + }catch(err){ + console.error(err); + } +} + + diff --git a/src/ClassDiagram.ts b/src/ClassDiagram.ts new file mode 100644 index 0000000..1a65faa --- /dev/null +++ b/src/ClassDiagram.ts @@ -0,0 +1,248 @@ +import * as vscode from 'vscode'; +import * as ejs from 'ejs'; +import * as path from 'path'; + +export default async function classDiagram(asyncapi: any, context: vscode.ExtensionContext) { + const templatePath = path.join(context.extensionPath, 'dist', 'components', 'ClassDiagram.ejs'); + let data: any = { + version: asyncapi?.asyncapi, + channels: [...Object.entries(asyncapi.channels || {}).map((v: any)=> [v[0], v[1].messages || {}]), ...Object.entries(asyncapi.components?.channels || {}).map((v: any)=> [v[0], v[1].messages || {}])], + operations: [...Object.entries(asyncapi.operations || {}), ...Object.entries(asyncapi.components?.operations || {})], + messages: [...new Set(Object.entries(asyncapi.components?.messages || {}))], + payloads: [], + others: [], + relations: [], + }; + + function replaceInvalidChars(str: string){ + return str.replace(/[`~!@#$%^&*()|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '_'); + } + + function getKeysFromEntries(arr: any){ + return arr.map((v: any[]) => v[0]); + } + + function recursiveObjectSplit(dest: any, source: any, parent: string) { + Object.entries(source || {}).map(([sourceName, sourceInfo]: [string, any]) => { + if (sourceName !== '$ref') { + if (!dest[dest.length - 1][1][sourceName]) { + Object.defineProperty(dest[dest.length - 1][1], sourceName, { value: sourceInfo }); + } + } + }); + Object.entries(source || {}).map(([sourceName, sourceInfo]: [string, any]) => { + if (sourceName === '$ref') { + let parsedRef = String(sourceInfo).split('/'); + if (Object.keys(asyncapi.components?.schemas || {}).indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1 ) { + let element = asyncapi.components?.schemas[parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')]; + if(typeof element === "object" && (String(element?.type).toLowerCase() === 'object' || !element?.type)) { + recursiveObjectSplit( + data.others, + asyncapi.components.schemas[parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')], + `${parent}.${parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')}` + ); + } + } + } else if (typeof sourceInfo === "object" && (String(sourceInfo?.type).toLowerCase() === 'object' || !sourceInfo?.type) ) { + + if(sourceInfo?.$ref){ + let parsedRef = String(sourceInfo?.$ref).split('/'); + if (Object.keys(asyncapi.components?.schemas || {}).indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1 ) { + let element = asyncapi.components?.schemas[parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')]; + if((String(element?.type).toLowerCase() === 'object' || !element?.type)){ + data.others.push([`${parent}.${sourceName}`, {}]); + data.relations.push( + `${replaceInvalidChars(parent)} <-- ${replaceInvalidChars(parent)}_${replaceInvalidChars(sourceName)}` + ); + recursiveObjectSplit(data.others, sourceInfo.properties, `${parent}.${sourceName}`); + }else{ + Object.defineProperty(dest[dest.length - 1][1][sourceName], 'type', { value: element.type }); + } + } + }else{ + data.others.push([`${parent}.${sourceName}`, {}]); + data.relations.push( + `${replaceInvalidChars(parent)} <-- ${replaceInvalidChars(parent)}_${replaceInvalidChars(sourceName)}` + ); + recursiveObjectSplit(data.others, sourceInfo.properties, `${parent}.${sourceName}`); + } + } + }); + } + + data.messages?.map(([messageName, messageInfo]: [string, any]) => { + if (!messageInfo.payload) { + return; + } + if (messageInfo.payload?.$ref) { + let parsedRef = String(messageInfo.payload?.$ref).split('/'); + let schemas = Object.entries(asyncapi.components?.schemas || {}); + if ( + schemas + .map(v => { + return v[0]; + }) + .indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1 + ) { + data.payloads.push([`${messageName}`, {}]); + recursiveObjectSplit( + data.payloads, + asyncapi.components?.schemas[parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')].properties, + `${messageName}` + ); + } + } else if (messageInfo.payload.schemaFormat) { + if (messageInfo.payload.schema?.$ref) { + let parsedRef = String(messageInfo.payload.schema.$ref).split('/'); + let schemas = Object.entries(asyncapi.components?.schemas || {}); + if ( + schemas + .map(v => { + return v[0]; + }) + .indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1 + ) { + data.payloads.push([ + `${messageName}`, + {}, + ]); + recursiveObjectSplit( + data.payloads, + asyncapi.components?.schemas[parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')].properties, + `${messageName}` + ); + } + } else { + data.payloads.push([`${messageName}`, {}]); + recursiveObjectSplit(data.payloads, messageInfo.payload.schema.properties, `${messageName}`); + } + } else { + data.payloads.push([`${messageName}`, {}]); + recursiveObjectSplit(data.payloads, messageInfo.payload.properties, `${messageName}`); + } + }); + + + Object.entries({ ...asyncapi.channels, ...asyncapi.components?.channels }).map(([channelName, channelInfo]: [string, any]) => { + if (!asyncapi.asyncapi || asyncapi.asyncapi?.split('.')[0] !== '2') { + Object.entries(channelInfo.messages || {}).map(([messageName, messageInfo]: [string, any]) => { + if (!messageInfo?.$ref) { + if (getKeysFromEntries(data.messages).indexOf(messageName) === -1) { + data.messages.push([messageName, messageInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars(messageName)}` + ); + } else { + data.messages.push([`${channelName}_${messageName}`, messageInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars(channelName)}_${replaceInvalidChars(messageName)}` + ); + } + } else { + let parsedRef = String(messageInfo?.$ref).split('/'); + if (getKeysFromEntries(data.messages).indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1) { + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars((parsedRef.pop() ?? '').replace(/~1/gi, '/'))}` + ); + } + } + }); + } else if (asyncapi.asyncapi?.split('.')[0] === '2') { + Object.entries({ subscribe: channelInfo.subscribe, publish: channelInfo.publish }).map( + ([operationName, operationInfo]: [string, any]) => { + if (!operationInfo) { + return; + } + data.operations.push([`${channelName}_${operationName}`, operationInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- OPERATION_${replaceInvalidChars(channelName)}_${replaceInvalidChars(operationName)}` + ); + if (operationInfo.message?.oneOf) { + operationInfo.message.oneOf.map((message: any) => { + Object.entries(message).map(([messageName, messageInfo]: [string, any]) => { + if (messageName === '$ref') { + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = + getKeysFromEntries(data.messages).indexOf(`${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}`) > -1 + ? `${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}` + : (parsedMsg.pop() ?? '').replace(/~1/gi, '/'); + if (getKeysFromEntries(data.messages).indexOf(finalMsgStr) > -1) { + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars((finalMsgStr ?? '').replace(/~1/gi, '/'))}` + ); + } + } else { + data.messages.push([`${channelName}_${messageName}`, messageInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars(channelName)}_${replaceInvalidChars(messageName)}` + ); + } + }); + }); + } else { + Object.entries(operationInfo.message).map(([messageName, messageInfo]: [string, any]) => { + if (messageName === '$ref') { + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = + getKeysFromEntries(data.messages).indexOf(`${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}`) > -1 + ? `${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}` + : (parsedMsg.pop() ?? '').replace(/~1/gi, '/'); + if (getKeysFromEntries(data.messages).indexOf(finalMsgStr) > -1) { + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars((finalMsgStr ?? '').replace(/~1/gi, '/'))}` + ); + } + } else { + if (getKeysFromEntries(data.messages).indexOf(messageName) === -1) { + data.messages.push([messageName, messageInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars(messageName)}` + ); + } else { + data.messages.push([`${channelName}_${messageName}`, messageInfo]); + data.relations.push( + `CHANNEL_${replaceInvalidChars(channelName)} <-- ${replaceInvalidChars(channelName)}_${replaceInvalidChars(messageName)}` + ); + } + } + }); + } + } + ); + } + }); + + Object.entries({ ...asyncapi.operations, ...asyncapi.components?.operations }).map( + ([operationName, operationInfo]: [string, any]) => { + if (asyncapi.asyncapi?.split('.')[0] !== '2') { + operationInfo.messages?.map((message: any) => { + Object.entries(message || {}).map(([messageName, messageInfo]: [string, any]) => { + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = + data.messages.map((v: any[]) => v[0]).indexOf(`${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}`) > -1 + ? `${parsedMsg[parsedMsg.length - 2]}_${parsedMsg[parsedMsg.length - 1]}` + : (parsedMsg.pop() || '').replace(/~1/gi, '/'); + if (data.messages.map((v: any[]) => v[0]).indexOf(finalMsgStr) > -1) { + data.relations.push( + `OPERATION_${replaceInvalidChars(operationName)} <-- ${replaceInvalidChars((finalMsgStr || '').replace(/~1/gi, '/'))}` + ); + } + }); + }); + + Object.entries(operationInfo.channel || {}).map(([channelName, channelInfo]: [string, any]) => { + let parsedRef = String(channelInfo).split('/'); + if (data.channels.map((v: any[]) => v[0]).indexOf(parsedRef[parsedRef.length - 1].replace(/~1/gi, '/')) > -1) { + data.relations.push( + `CHANNEL_${replaceInvalidChars((parsedRef.pop() ?? '').replace(/~1/gi, '/'))} <-- OPERATION_${replaceInvalidChars(operationName)}` + ); + } + }); + } + } + ); + return await ejs.renderFile(templatePath, { + ...data, + }); +} + diff --git a/src/Diagnostics.ts b/src/Diagnostics.ts new file mode 100644 index 0000000..5d2a4f7 --- /dev/null +++ b/src/Diagnostics.ts @@ -0,0 +1,34 @@ +import { ISpectralDiagnostic } from '@stoplight/spectral-core'; +import * as vscode from 'vscode'; +import * as ejs from 'ejs'; +import * as path from 'path'; + +export default async function diagnosticsMarkdown(diagnostics: ISpectralDiagnostic[], context: vscode.ExtensionContext){ + const templatePath = path.join(context.extensionPath,'dist', 'components','Diagnostics.ejs'); + let data: object[] = []; + let recentErrorPath: string = ""; + let joinedPath: string = ""; + diagnostics.forEach(diagnostic =>{ + joinedPath = diagnostic.path.join(' / '); + if(joinedPath.indexOf(recentErrorPath) === -1 || !recentErrorPath){ + recentErrorPath = joinedPath; + data.push({ + code: diagnostic.code, + message: diagnostic.message, + path: recentErrorPath, + severity: diagnostic.severity, + source: diagnostic.source + }); + }else{ + recentErrorPath = joinedPath; + data[data.length - 1] = { + code: diagnostic.code, + message: diagnostic.message, + path: recentErrorPath, + severity: diagnostic.severity, + source: diagnostic.source + }; + } + }); + return await ejs.renderFile(templatePath, {data}); +} \ No newline at end of file diff --git a/src/Flowchart.ts b/src/Flowchart.ts new file mode 100644 index 0000000..4496a59 --- /dev/null +++ b/src/Flowchart.ts @@ -0,0 +1,109 @@ +import * as vscode from 'vscode'; +import * as ejs from 'ejs'; +import * as path from 'path'; + +export default async function flowchart(asyncapi: any, context: vscode.ExtensionContext) { + + const templatePath = path.join(context.extensionPath,'dist', 'components','Flowchart.ejs'); + let data: any = { + title: asyncapi?.info?.title, + servers:[...Object.keys(asyncapi.servers || {}),...Object.keys(asyncapi.components?.servers || {})], + channels:[...Object.keys(asyncapi.channels || {}),...Object.keys(asyncapi.components?.channels || {})], + operations:[...Object.entries(asyncapi.operations || {}).map((value: any)=> `${value[1].action || "send"}:${value[0]}`),...Object.entries(asyncapi.components?.operations || {}).map((value: any)=> `${value[1].action || "send"}:${value[0]}`)], + messages:[...new Set(Object.keys(asyncapi.components?.messages || {}))], + relations:[] + }; + Object.entries({...asyncapi.channels,...asyncapi.components?.channels} || {}).map(([channelName, channelInfo]: [string, any]) => { + if(!asyncapi.asyncapi || asyncapi.asyncapi?.split('.')[0] !== '2'){ + Object.entries(channelInfo.messages || {}).map(([messageName, messageInfo]: [string, any]) =>{ + if(!messageInfo?.$ref) { + if(data.messages.indexOf(messageName) === -1){ + data.messages.push(messageName); + data.relations.push(`${messageName} --> ${channelName}`); + } + else { + data.messages.push(`${channelName}_${messageName}`); + data.relations.push(`${channelName}_${messageName} --> ${channelName}`); + } + }else{ + let parsedRef = String(messageInfo?.$ref).split('/'); + if(data.messages.indexOf(parsedRef[parsedRef.length-1].replace(/~1/gi,"/")) > -1){ + data.relations.push(`${parsedRef[parsedRef.length-1].replace(/~1/gi,"/")} --> ${channelName}`); + } + } + }); + channelInfo.servers?.map((server: any)=>{ + let parsedRef = String(server?.$ref).split('/'); + if(data.servers.indexOf(parsedRef[parsedRef.length-1].replace(/~1/gi,"/")) > -1) + {data.relations.push(`${channelName} --> ${(parsedRef.pop() || "").replace(/~1/gi,"/")}`);} + + }); + }else if(asyncapi.asyncapi?.split('.')[0] === '2'){ + Object.entries({"subscribe": channelInfo.subscribe, "publish": channelInfo.publish} || {}).map(([operationName, operationInfo] : [string, any])=>{ + if(!operationInfo) {return;} + data.operations.push(`${operationInfo.action || "send"}:${operationName}`); + data.relations.push(`${operationInfo.action || "send"}:${operationName} --> ${channelName}`); + if(operationInfo.message?.oneOf){ + operationInfo.message.oneOf.map((message: any)=>{ + Object.entries(message).map(([messageName,messageInfo]:[string,any])=>{ + if(messageName === '$ref'){ + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = (data.messages.indexOf(`${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`) > -1)? `${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`: ( parsedMsg.pop() || "").replace(/~1/gi,"/"); + if(data.messages.indexOf(finalMsgStr) > -1) { + data.relations.push(`${finalMsgStr} --> ${channelName}`); + } + }else{ + data.messages.push(`${channelName}_${messageName}`); + data.relations.push(`${channelName}_${messageName} --> ${channelName}`); + } + }); + }); + }else{ + Object.entries(operationInfo.message).map(([messageName, messageInfo]:[string, any])=>{ + if(messageName === '$ref'){ + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = (data.messages.indexOf(`${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`) > -1)? `${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`: (parsedMsg.pop() || "").replace(/~1/gi,"/"); + if(data.messages.indexOf((finalMsgStr || "").replace(/~1/gi,"/")) > -1) { + data.relations.push(`${(finalMsgStr || "").replace(/~1/gi,"/")} --> ${channelName}`); + } + }else{ + if(data.messages.indexOf(messageName) === -1){ + data.messages.push(`${messageName}`); + data.relations.push(`${messageName} --> ${channelName}`); + }else{ + data.messages.push(`${channelName}_${messageName}`); + data.relations.push(`${channelName}_${messageName} --> ${channelName}`); + } + } + }); + } + }); + channelInfo.servers?.map((server: any)=>{ + if(typeof server === "string") + {data.relations.push(`${channelName} --> ${server.replace(/~1/gi,"/")}`);} + }); + } + }); + + Object.entries({...asyncapi.operations, ...asyncapi.components?.operations} || {}).map(([operationName, operationInfo]: [string, any]) => { + if(asyncapi.asyncapi?.split('.')[0] !== '2'){ + operationInfo.messages?.map((message: any)=> { + Object.entries(message || {}).map(([messageName, messageInfo]: [string, any])=>{ + let parsedMsg = String(messageInfo).split('/'); + let finalMsgStr = (data.messages.indexOf(`${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`) > -1)? `${parsedMsg[parsedMsg.length-2].replace(/~1/gi,"/")}_${parsedMsg[parsedMsg.length-1].replace(/~1/gi,"/")}`: (parsedMsg.pop() || "").replace(/~1/gi,"/"); + if(data.messages.indexOf(finalMsgStr) > -1) + {data.relations.push(`${finalMsgStr?.replace(/~1/gi,"/")} --> ${operationInfo.action || "send"}:${operationName}`);} + }); + }); + + Object.entries(operationInfo.channel || {}).map(([channelName, channelInfo]: [string, any])=>{ + let parsedRef = String(channelInfo).split('/'); + if(data.channels.indexOf(parsedRef[parsedRef.length-1].replace(/~1/gi,"/")) > -1) + {data.relations.push(`${operationInfo.action || "send"}:${operationName} --> ${(parsedRef.pop() || "").replace(/~1/gi,"/")}`);} + }); + } + }); + return await ejs.renderFile(templatePath,{ + ...data + }); +} \ No newline at end of file diff --git a/src/PreviewMarkdown.ts b/src/PreviewMarkdown.ts index fbf2805..d909ec6 100644 --- a/src/PreviewMarkdown.ts +++ b/src/PreviewMarkdown.ts @@ -1,28 +1,77 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as fs from 'fs'; import { isAsyncAPIFile } from './PreviewWebPanel'; - +import Diagnostics from './Diagnostics'; import { Parser, fromFile, AsyncAPIDocumentInterface } from '@asyncapi/parser'; +import { AvroSchemaParser } from '@asyncapi/avro-schema-parser'; import Asyncapi from './Asyncapi'; - +import { ISpectralDiagnostic } from '@stoplight/spectral-core'; +import { parse } from 'yaml'; +import Flowchart from './Flowchart'; +import ClassDiagram from './ClassDiagram'; const parser = new Parser(); +parser.registerSchemaParser(AvroSchemaParser()); - -async function buildMarkdown(document:AsyncAPIDocumentInterface | undefined, context: vscode.ExtensionContext){ +function parsedAsyncapiPreview(){ + + let editor: any = vscode.window.activeTextEditor; + const document = editor.document; + const filePath: any = document?.fileName; + const fullPath = path.resolve(filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + + + let parsedData; + if (filePath.endsWith('.json')) { + parsedData = JSON.parse(content); + } else if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) { + parsedData = parse(content); + } else { + vscode.window.showInformationMessage('Unsupported file type.'); + return; + } + return parsedData || ""; +} - let content = ''; +async function buildMarkdown(document: any, diagnostics: ISpectralDiagnostic[], context: vscode.ExtensionContext){ + + + let content: any = ''; + if(document !== undefined){ - - content = await Asyncapi(document, context); + document.isAsyncapiParser = true; + content = await Asyncapi(document, context) || ""; + }else{ + let parsedData: any = parsedAsyncapiPreview(); + while(!parsedData) { + parsedData = parsedAsyncapiPreview(); + } + parsedData.isAsyncapiParser = false; + content = await Diagnostics(diagnostics, context); + content += await Asyncapi(parsedData, context) || ""; } - + return content; } +async function buildDiagrams(context: vscode.ExtensionContext){ + let parsedData: any = parsedAsyncapiPreview(); + let flowchart; + let classDiagram; + + if(parsedData){ + flowchart = await Flowchart(parsedData, context); + classDiagram = await ClassDiagram(parsedData, context); + } + + return {flowchart, classDiagram}; +} + export function previewMarkdown(context: vscode.ExtensionContext) { return async (uri: vscode.Uri) => { uri = uri || (await promptForAsyncapiFile()) as vscode.Uri; @@ -39,7 +88,7 @@ export const openAsyncapiMdFiles: { [id: string]: vscode.WebviewPanel } = {}; // export async function openAsyncAPIMarkdown(context: vscode.ExtensionContext, uri: vscode.Uri) { const localResourceRoots = [ vscode.Uri.file(path.dirname(uri.fsPath)), - vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/mermaid/dist/mermaid.min.js') + vscode.Uri.joinPath(context.extensionUri, 'dist') ]; if (vscode.workspace.workspaceFolders) { vscode.workspace.workspaceFolders.forEach(folder => { @@ -54,12 +103,51 @@ export async function openAsyncAPIMarkdown(context: vscode.ExtensionContext, uri localResourceRoots, }); - const { document } = await fromFile(parser, uri.fsPath).parse(); - let result = await buildMarkdown(document, context); - + try{ + const { document, diagnostics } = await fromFile(parser, uri.fsPath).parse(); + + let result = await buildMarkdown(document, diagnostics, context); + let {flowchart, classDiagram} = await buildDiagrams(context); + + panel.title = path.basename(uri.fsPath); + panel.webview.html = getWebviewContent(context, panel.webview, uri, result, flowchart, classDiagram); + } catch(err) { + panel.title = path.basename(uri.fsPath); + panel.webview.html = ` + + + + + Centered Error Box + + + +
+

${err}

+
+ + `; + } - panel.title = path.basename(uri.fsPath); - panel.webview.html = getWebviewContent(context, panel.webview, uri, result); panel.onDidDispose(() => { delete openAsyncapiMdFiles[uri.fsPath]; @@ -83,175 +171,54 @@ async function promptForAsyncapiFile() { return uris?.[0]; } -function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Webview, asyncapiFile: vscode.Uri, result:any) { +function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Webview, asyncapiFile: vscode.Uri, result: any, flowchart: any, classDiagram: any) { const mermaidJs = webview.asWebviewUri( vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/mermaid/dist/mermaid.min.js') ); + const panzoomJs = webview.asWebviewUri( + vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/panzoom/dist/panzoom.min.js') + ); + const turndownJs = webview.asWebviewUri( + vscode.Uri.joinPath(context.extensionUri, 'dist/node_modules/turndown/dist/turndown.js') + ); + const globalsCSS = webview.asWebviewUri( + vscode.Uri.joinPath(context.extensionUri, 'dist/globals.css') + ); const html = ` - + - +
+
<
+
>
+
+
+

Index

+ Info +

Operations

+
${result}
-
Section 2
-
Section 3
+
${flowchart}
+
${classDiagram}
+
+
+
+
+
-
+
+
+
+
+
+
Zoom : 1
+
Scroll : 0, 0
+
Zoom : 1
+
Scroll : 0, 0
+
@@ -260,14 +227,319 @@ function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Web
- +
Copy
+
Copied!
+ + + + diff --git a/src/components/Asyncapi.ejs b/src/components/Asyncapi.ejs index 983bdf1..f8849f3 100644 --- a/src/components/Asyncapi.ejs +++ b/src/components/Asyncapi.ejs @@ -1,3 +1,5 @@ -<%- include(path.infoPath,{...info,tagsPath: path.tagsPath}) %> -<%- include(path.serversPath,{...servers, ...path}) %> -<%- include(path.operationsPath,{...operations, ...path}) %> \ No newline at end of file +
+ <%- include(path.infoPath,{...info,tagsPath: path.tagsPath}) %> + <%- include(path.serversPath,{...servers, ...path}) %> + <%- include(path.operationsPath,{...operations, ...path}) %> +
\ No newline at end of file diff --git a/src/components/Diagnostics.ejs b/src/components/Diagnostics.ejs new file mode 100644 index 0000000..4494827 --- /dev/null +++ b/src/components/Diagnostics.ejs @@ -0,0 +1,22 @@ +

Error Loading Preview

+<% data.forEach(diagnostic => { %> +
+ <% if(diagnostic.severity == 0) { %> +
+ <% } else if(diagnostic.severity == 1) { %> +
+ <% }else if(diagnostic.severity == 2) { %> +
+ <% }else if(diagnostic.severity == 3) { %> +
+ <% } %> + +
+

⚠️<%= diagnostic.message %>

+

<%= diagnostic.path %>

+
<%= diagnostic.code %>
+

Source: <%= diagnostic.source %>

+

Severity: <%= diagnostic.severity %>

+
+
+<% }) %> \ No newline at end of file diff --git a/src/components/Info.ejs b/src/components/Info.ejs index a4d499c..7bcb6ed 100644 --- a/src/components/Info.ejs +++ b/src/components/Info.ejs @@ -1,43 +1,41 @@ -

<%= title %> <%= version %> documentation

-<% if(extensions && extensions.has('x-logo')) { %> -  logo -<% } %> +

<%= title %> <%= version %> documentation

+ <% } %> <% if(message.hasDescription()) { %> <%- md.render(message.description()) %> <% } %> - <% if(externalDocs) { %> - - <%- md.render(externalDocs.description() || 'Find more info here.') %> + <% if(externalDocs && externalDocs?.url()) { %> + + <%- md.render(externalDocs?.description() || 'Find more info here.') %> <% } %> - <% if(headers) { %> + <% if(headers && !headers.incorrect) { %>
Headers
- <%- include(schemaPath,{schema:headers,schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> + <%- include(schemaPath,{schema:headers.schema? headers.schema : headers,schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> <% const ex = messageHelper.getHeadersExamples(message); if (ex) { %>
Examples of headers
@@ -76,11 +73,16 @@ <% } %> <% } %> - <% } %> + <% }else if(headers?.incorrect && Object.values(headers || []).length > 1) { %> +
+
Headers
+ +
+ <% } %> - <% if(payload) { %> + <% if(payload && !payload.incorrect) { %>
Payload
- <%- include(schemaPath,{schema:payload, schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> + <%- include(schemaPath,{schema:payload.schema? payload.schema : payload, schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> <% const examples = messageHelper.getPayloadExamples(message); if (examples) { %>
Examples of payload
@@ -92,7 +94,7 @@ <% }); %> <% } %> <% }else { - const payload = message.payload(); + const payload = message?.payload(); if (payload) { %>
Examples of payload _generated_
<%- md.render(messageHelper.generateExample(payload.json())) %> @@ -100,10 +102,42 @@ <% } %> - <% } %> + <% }else if(payload?.incorrect && Object.values(payload || []).length > 1) { %> +
+
Payload
+ +
+ <% } %> + + <% if(traits?.incorrect && Object.values(traits || []).length > 1) { %> +
+
Message Traits
+ +
+ <% } %> <%- include(bindingsPath,{name:"Message specific information", bindings:message.bindings(), schemaHelper, schemaPath}) %> <%- include(extensionsPath,{name:"Message extensions", extensions:message.extensions(), schemaHelper, schemaPath}) %> <%- include(tagsPath,{name:"Message tags", tags:message.tags()}) %> <% } %> +<% function renderObject(obj, indent) { %> + <% for (let key in obj) { %> + <% if (key != 'incorrect') { %> + <% if (typeof obj[key] === 'object' && obj[key] !== null) { %> +
  • + <%= key %>: + +
  • + <% } else { %> +
  • + <%= key %>: + <%= obj[key] %> +
  • + <% } %> + <% } %> + <% } %> +<% } %> + diff --git a/src/components/Operations.ejs b/src/components/Operations.ejs index fc7c6b7..88db994 100644 --- a/src/components/Operations.ejs +++ b/src/components/Operations.ejs @@ -6,84 +6,106 @@ <% 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) { + if (operation.reply()) { type = 'request'; } else { type = 'send'; } } else if (operation.isReceive()) { - if (operation.reply() !== undefined) { + if (operation.reply()) { 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,{ schema: parameters, schemaName: "Parameters", hideTitle: true, schemaHelper, md, path:"" }) %> - <% } %> - - <%- include(securityPath,{ header:'Additional security requirements', protocol: null, security: operation.security(), serverHelper, md }) %> - - - <%- include(bindingsPath,{ name:"Channel specific information", bindings: channel.bindings(), schemaHelper, schemaPath, md }) %> - <%- include(bindingsPath,{ name:"Operation specific information", bindings: operation.bindings(), schemaHelper, schemaPath, md }) %> - <%- include(extensionsPath,{ name:"Channel extensions", extensions: channel.extensions(), schemaHelper, schemaPath, md }) %> - <%- include(extensionsPath,{ name:"Operation extensions", extensions: operation.extensions(), schemaHelper, schemaPath, md }) %> - - - <% const messages = operation.messages().all(); %> - <% if (messages.length !== 0) { %> - <% const messageText = getOperationMessageText({type}); %> - <% if(messages.length > 1) { %> -

    <%- md.render(messageText) %>

    +
    + <%- md.render(`${getRenderedTypeForOperation({type})} \`${channel.address()}\` Operation`) %> + <% if(operation.summary()) { %> + <%- md.render(`*${operation.summary().trim()}*`) %> + <% } %> + + <% if(channel.hasDescription()) { %> + <%- md.render(channel.description()) %> + <% } %> + <% if(operation.hasDescription()) { %> + <%- md.render(operation.description()) %> <% } %> - <% for(let message of messages) { %> - <%- include(messagePath,{message, bindingsPath, extensionsPath, schemaHelper, schemaPath, md, messageHelper}) %> + <% if(operation.externalDocs()?.hasDescription() ) { %> + <%= (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,{ schema: parameters, schemaName: "Parameters", hideTitle: true, schemaHelper, md, path:"" }) %> + <% } %> + + <%- include(securityPath,{ header:'Additional security requirements', protocol: null, security: operation.security(), serverHelper, md }) %> + + + <%- include(bindingsPath,{ name:"Channel specific information", bindings: channel.bindings(), schemaHelper, schemaPath, md }) %> + <%- include(bindingsPath,{ name:"Operation specific information", bindings: operation.bindings(), schemaHelper, schemaPath, md }) %> + <%- include(extensionsPath,{ name:"Channel extensions", extensions: channel.extensions(), schemaHelper, schemaPath, md }) %> + <%- include(extensionsPath,{ name:"Operation extensions", extensions: operation.extensions(), schemaHelper, schemaPath, md }) %> + + + <% const messages = operation.messages().all(); %> + <% if (messages.length !== 0) { %> + <% const messageText = getOperationMessageText({type}); %> + <% if(messages.length > 1) { %> +

    <%- md.render(messageText) %>

    + <% } %> + <% for(let message of messages) { %> + <%- include(messagePath,{message, bindingsPath, extensionsPath, schemaHelper, schemaPath, md, messageHelper}) %> + <% } %> + <% } %> + + <% if(operation.traits().incorrect && Object.values(operation.traits() || []).length > 1) { %> +
    +
    Operation Traits
    +
      <%= renderObject(operation.traits(), 0) %>
    +
    <% } %> - +
    +
    <% } %> <% } %> <% } %> <% } %> - +<% function renderObject(obj, indent) { %> + <% for (let key in obj) { %> + <% if (key != 'incorrect') { %> + <% if (typeof obj[key] === 'object' && obj[key] !== null) { %> +
  • + <%= key %>: + +
  • + <% } else { %> +
  • + <%= key %>: + <%= obj[key] %> +
  • + <% } %> + <% } %> + <% } %> +<% } %> <% function getRenderedTypeForOperation({type}) { if (isV3) { diff --git a/src/components/Schema.ejs b/src/components/Schema.ejs index 8b9f2a0..021bb13 100644 --- a/src/components/Schema.ejs +++ b/src/components/Schema.ejs @@ -333,7 +333,6 @@ function tree(path = '') { } function buildPath(path = '', field = '') { - console.log(path,field); if (!path) return field; return `${path}.${field}`; } diff --git a/src/components/Security.ejs b/src/components/Security.ejs index 53eeb15..b670cc1 100644 --- a/src/components/Security.ejs +++ b/src/components/Security.ejs @@ -27,14 +27,14 @@ const type = securitySchema?.type() && serverHelper.securityType(securitySchema.type()); %>