diff --git a/src/Asyncapi.ts b/src/Asyncapi.ts
index 2f8cf37..bacef3b 100644
--- a/src/Asyncapi.ts
+++ b/src/Asyncapi.ts
@@ -4,6 +4,7 @@ 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');
@@ -22,7 +23,341 @@ const jsonSchemaTypes: string[] = [
'null',
];
+const RESTRICTED_ANY = 'restricted any';
+const NEVER = 'never';
+const UNKNOWN = 'unknown';
+const ANY = 'any';
+const jsonSchemaKeywordTypes = {
+ maxLength: 'string',
+ minLength: 'string',
+ pattern: 'string',
+ contentMediaType: 'string',
+ contentEncoding: 'string',
+ multipleOf: 'number',
+ maximum: 'number',
+ exclusiveMaximum: 'number',
+ minimum: 'number',
+ exclusiveMinimum: 'number',
+ items: 'array',
+ maxItems: 'array',
+ minItems: 'array',
+ uniqueItems: 'array',
+ contains: 'array',
+ additionalItems: 'array',
+ maxProperties: 'object',
+ minProperties: 'object',
+ required: 'object',
+ properties: 'object',
+ patternProperties: 'object',
+ propertyNames: 'object',
+ dependencies: 'object',
+ additionalProperties: 'object',
+};
+
class SchemaHelper {
+
+ 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
+ if (Object.keys(schema.json()).length === 0) {
+ return ANY;
+ }
+ // handle case with `{ not: {}, ... }` schemas
+ const not = schema.not();
+ if (not && this.inferType(not) === ANY) {
+ return NEVER;
+ }
+
+ let type = this.inferType(schema);
+ if (Array.isArray(type)) {
+ return type.map(t => this.toType(t, schema)).join(' | ');
+ }
+ type = this.toType(type, schema);
+ const combinedType = this.toCombinedType(schema);
+
+ if (type && combinedType) {
+ return `${type} ${combinedType}`;
+ }
+ if (combinedType) {
+ return combinedType;
+ }
+ return type;
+ }
+
+ 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)) {
+ const types = items.map(item => this.toSchemaType(item)).join(', ');
+ const additionalItems = schema.additionalItems();
+ if (additionalItems === true) {
+ return `tuple<${types || UNKNOWN}, ...optional<${ANY}>>`;
+ }
+ if (additionalItems === false) {
+ return `tuple<${types}>`;
+ }
+ const additionalType = this.toSchemaType(additionalItems);
+ return `tuple<${types || UNKNOWN}, ...optional<${additionalType}>>`;
+ }
+ if (!items) {
+ return `array<${ANY}>`;
+ }
+ return `array<${this.toSchemaType(items) || UNKNOWN}>`;
+ }
+ return type;
+ }
+
+ static toCombinedType(schema: { json?: () => boolean; isBooleanSchema?: () => any; not?: () => any; oneOf?: any; anyOf?: any; allOf?: any; }) {
+ const t = [];
+ if (schema.oneOf()) {
+ t.push('oneOf');
+ }
+ if (schema.anyOf()) {
+ t.push('anyOf');
+ }
+ if (schema.allOf()) {
+ t.push('allOf');
+ }
+ if (t.length === 0 || t.length > 1) {
+ return undefined;
+ }
+ 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; }) {
+ let types = schema.type();
+
+ if (types !== undefined) {
+ if (Array.isArray(types)) {
+ // if types have `integer` and `number` types, `integer` is unnecessary
+ if (types.includes('integer') && types.includes('number')) {
+ types = types.filter(t => t !== 'integer');
+ }
+ return types.length === 1 ? types[0] : types;
+ }
+ return types;
+ }
+
+ const constValue = schema.const();
+ if (constValue !== undefined) {
+ const typeOf = typeof constValue;
+ if (typeOf === 'number' && Number.isInteger(constValue)) {
+ return 'integer';
+ }
+ return typeOf;
+ }
+ 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;
+ })));
+ return inferredType.length === 1 ? inferredType[0] : inferredType;
+ }
+
+ const schemaKeys = Object.keys(schema.json() || {}) || [];
+ const hasInferredTypes = Object.keys(jsonSchemaKeywordTypes).some(key =>
+ schemaKeys.includes(key),
+ );
+ if (hasInferredTypes === true) {
+ return '';
+ }
+ if (this.toCombinedType(schema)) {
+ return '';
+ }
+ return ANY;
+ }
+
+ static prettifyValue(value: { toString: () => any; }) {
+ const typeOf = typeof value;
+ if (typeOf === 'string') {
+ return `"${value}"`;
+ }
+ if (typeOf === 'number' || typeOf === 'bigint' || typeOf === 'boolean') {
+ return value;
+ }
+ if (Array.isArray(value)) {
+ return `[${value.toString()}]`;
+ }
+ 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; }) {
+ const constraints = [];
+
+ // related to number/integer
+ const numberRange = this.humanizeNumberRangeConstraint(
+ schema.minimum(),
+ schema.exclusiveMinimum(),
+ schema.maximum(),
+ schema.exclusiveMaximum(),
+ );
+ if (numberRange !== undefined) {
+ constraints.push(numberRange);
+ }
+ const multipleOfConstraint = this.humanizeMultipleOfConstraint(
+ schema.multipleOf(),
+ );
+ if (multipleOfConstraint !== undefined) {
+ constraints.push(multipleOfConstraint);
+ }
+
+ // related to string
+ 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(),
+ );
+ if (arrayRange !== undefined) {
+ constraints.push(arrayRange);
+ }
+
+ // related to object
+ const objectRange = this.humanizeRangeConstraint(
+ 'properties',
+ schema.minProperties(),
+ schema.maxProperties(),
+ );
+ if (objectRange !== undefined) {
+ constraints.push(objectRange);
+ }
+
+ return constraints;
+ }
+
+ static humanizeNumberRangeConstraint(
+ min: undefined,
+ exclusiveMin: undefined,
+ max: undefined,
+ exclusiveMax: undefined,
+ ) {
+ const hasExclusiveMin = exclusiveMin !== undefined;
+ const hasMin = min !== undefined || hasExclusiveMin;
+ const hasExclusiveMax = exclusiveMax !== undefined;
+ const hasMax = max !== undefined || hasExclusiveMax;
+
+ let numberRange;
+ if (hasMin && hasMax) {
+ numberRange = hasExclusiveMin ? '( ' : '[ ';
+ numberRange += hasExclusiveMin ? exclusiveMin : min;
+ numberRange += ' .. ';
+ numberRange += hasExclusiveMax ? exclusiveMax : max;
+ numberRange += hasExclusiveMax ? ' )' : ' ]';
+ } else if (hasMin) {
+ numberRange = hasExclusiveMin ? '> ' : '>= ';
+ numberRange += hasExclusiveMin ? exclusiveMin : min;
+ } else if (hasMax) {
+ numberRange = hasExclusiveMax ? '< ' : '<= ';
+ numberRange += hasExclusiveMax ? exclusiveMax : max;
+ }
+ return numberRange;
+ }
+
+ static humanizeMultipleOfConstraint(
+ multipleOf: { toString: (arg0: number) => any; } | undefined,
+ ) {
+ if (multipleOf === undefined) {
+ return;
+ }
+ const strigifiedMultipleOf = multipleOf.toString(10);
+ if (!(/^0\.0*1$/).test(strigifiedMultipleOf)) {
+ return `multiple of ${strigifiedMultipleOf}`;
+ }
+ return `decimal places <= ${strigifiedMultipleOf.split('.')[1].length}`;
+ }
+
+ static humanizeRangeConstraint(
+ description: any,
+ min: number | undefined,
+ max: undefined,
+ ) {
+ let stringRange;
+ if (min !== undefined && max !== undefined) {
+ if (min === max) {
+ stringRange = `${min} ${description}`;
+ } else {
+ stringRange = `[ ${min} .. ${max} ] ${description}`;
+ }
+ } else if (max !== undefined) {
+ stringRange = `<= ${max} ${description}`;
+ } else if (min !== undefined) {
+ if (min === 1) {
+ stringRange = 'non-empty';
+ } else {
+ stringRange = `>= ${min} ${description}`;
+ }
+ }
+ return stringRange;
+ }
+
+ static getDependentRequired(propertyName: string, schema: { dependencies: () => any; }) {
+ const dependentRequired = [];
+ const dependencies = schema.dependencies();
+ if (!dependencies) {
+ return;
+ }
+
+ for (const [prop, array] of Object.entries(dependencies)) {
+ if (Array.isArray(array) && array.includes(propertyName)) {
+ dependentRequired.push(prop);
+ }
+ }
+ return dependentRequired.length ? dependentRequired : undefined;
+ }
+
+ static getDependentSchemas(schema: { dependencies: () => any; }) {
+ const dependencies = schema.dependencies();
+ if (!dependencies) {
+ return;
+ }
+
+ const records:any = {};
+ for (const [prop, propSchema] of Object.entries(dependencies)) {
+ if (typeof propSchema === 'object' && !Array.isArray(propSchema)) {
+ records[String(prop)] = propSchema;
+ }
+ }
+ if (!Object.keys(records).length) {
+ return undefined;
+ }
+
+ const json:object = {
+ type: 'object',
+ 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;
@@ -218,7 +553,61 @@ class ServerHelper {
}
}
+class MessageHelper {
+ 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;}
+ return {
+ name: e.name(),
+ summary: e.summary(),
+ example: e.payload(),
+ };
+ })
+ .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; }) {
+ const examples = message.examples().all();
+ if (Array.isArray(examples) && examples.some(e => e.headers())) {
+ const messageExamples = examples
+ .map(e => {
+ if (!e.headers()) {return;}
+ return {
+ name: e.name(),
+ summary: e.summary(),
+ example: e.headers(),
+ };
+ })
+ .filter(Boolean);
+
+ if (messageExamples.length > 0) {
+ return messageExamples;
+ }
+ }
+
+ const headers = message.headers();
+ if (headers?.examples()) {
+ return headers.examples().map((example: any) => ({ example }));
+ }
+ }
+ static generateExample(value: any, options: any) {
+ return JSON.stringify(sample(value, options || {}) || '', null, 2);
+ }
+}
export default async function info(asyncapi:AsyncAPIDocumentInterface, context: vscode.ExtensionContext) {
@@ -253,6 +642,7 @@ export default async function info(asyncapi:AsyncAPIDocumentInterface, context:
isV3: asyncapi.version().split('.')[0] === '3',
schemaHelper: SchemaHelper,
serverHelper: ServerHelper,
+ messageHelper: MessageHelper,
allServersLength: asyncapi.servers().all().length,
md
},
diff --git a/src/components/Bindings.ejs b/src/components/Bindings.ejs
index c927f9c..419eb2a 100644
--- a/src/components/Bindings.ejs
+++ b/src/components/Bindings.ejs
@@ -1,5 +1,5 @@
<% if (!bindings.isEmpty()) { %>
<% for(let binding of bindings.all()){ %>
- <%- include(schemaPath,{ schemaName:`${binding.protocol().charAt(0).toUpperCase() + binding.protocol().slice(1)} ${name}`, schema: schemaHelper.jsonToSchema(binding), key: binding.protocol() }) %>
+ <%- include(schemaPath,{ schemaName:`${binding.protocol().charAt(0).toUpperCase() + binding.protocol().slice(1)} ${name}`, schema: schemaHelper.jsonToSchema(binding), hideTitle: false, schemaHelper, md, path:"" }) %>
<% } %>
<% } %>
\ No newline at end of file
diff --git a/src/components/Extensions.ejs b/src/components/Extensions.ejs
index 9a25262..b678539 100644
--- a/src/components/Extensions.ejs
+++ b/src/components/Extensions.ejs
@@ -1,3 +1,3 @@
<% if (Object.keys(schemaHelper.getCustomExtensions(extensions) || {}).length !== 0) { %>
- <%- include(schemaPath,{ schemaName:name, schema: schemaHelper.jsonToSchema(extensions) }) %>
+ <%- include(schemaPath,{ schemaName:name, schema: schemaHelper.jsonToSchema(extensions), hideTitle: false, schemaHelper, md, path:"" }) %>
<% } %>
\ No newline at end of file
diff --git a/src/components/Message.ejs b/src/components/Message.ejs
index e69de29..78b56ba 100644
--- a/src/components/Message.ejs
+++ b/src/components/Message.ejs
@@ -0,0 +1,109 @@
+<% if (message) {
+ const messageId = message.id();
+ const headers = message.headers();
+ const payload = message.payload();
+ const correlationId = message.correlationId();
+ const contentType = message.contentType();
+ const externalDocs = message.externalDocs();
+ const showInfoList = contentType || externalDocs;
+
+ let header = 'Message';
+ if (message.title()) {
+ header += ` ${message.title()}`;
+ }
+ const id = message.name() || messageId;
+ if (id) {
+ header += ` ${id}
`;
+ } %>
+
+
Examples of headers+ <% if (ex.length !== 0) { + examples.map((e, idx) => { %> +
<%= e.name && e.name %>
+ <%- e.summary && md.render(e.summary) %> +<%= JSON.stringify(e.example, null, 2) %>
+ <% }); %>
+ <% } %>
+ <% }else {
+ const headers = message.headers();
+ if (headers) { %>
+ Examples of headers _generated_+
<%- md.render(messageHelper.generateExample(headers.json())) %>
+ <% } %>
+ <% } %>
+
+ <% } %>
+
+ <% if(payload) { %>
+ Examples of payload+ <% if (examples.length !== 0) { + examples.map((ex, idx) => { %> +
<%= ex.name && ex.name %>
+ <%- ex.summary && md.render(ex.summary) %> +<%= md.render(JSON.stringify(ex.example, null, 2)) %>
+ <% }); %>
+ <% } %>
+ <% }else {
+ const payload = message.payload();
+ if (payload) { %>
+ Examples of payload _generated_+
<%- md.render(messageHelper.generateExample(payload.json())) %>
+ <% } %>
+
+ <% } %>
+
+ <% } %>
+
+ <%- 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()}) %>
+<% } %>
+
diff --git a/src/components/Operations.ejs b/src/components/Operations.ejs
index 98fca70..fc7c6b7 100644
--- a/src/components/Operations.ejs
+++ b/src/components/Operations.ejs
@@ -55,26 +55,26 @@
<% const parameters = schemaHelper.parametersToSchema(channel.parameters().all()); %>
<% if(parameters) { %>
<%= messageText %>
+<%- md.render(messageText) %>
<% } %> <% for(let message of messages) { %> - <%- include(messagePath,{message}) %> + <%- include(messagePath,{message, bindingsPath, extensionsPath, schemaHelper, schemaPath, md, messageHelper}) %> <% } %> <% } %> diff --git a/src/components/Schema.ejs b/src/components/Schema.ejs index e69de29..6e2925a 100644 --- a/src/components/Schema.ejs +++ b/src/components/Schema.ejs @@ -0,0 +1,420 @@ +<% if(schemaName && hideTitle === false ) { %> +Name | +Type | +Description | +Value | +Constraints | +Notes | +
---|---|---|---|---|---|
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +
<%- (renderedName || '-') %> | +<%- (schemaType || '-') %> | +<%- (md.render(description) || '-') %> | +<%- (values || '-') %> | +<%- (constraints || '-') %> | +<%- (notes || '-') %> | +