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}`; + } %> + +

<%- header %>

+ + <% if(message.summary()) { %> + <%- md.render(`*${message.summary()}*`) %> + <% } %> + + <% if(showInfoList) { %> +
+ <% if(messageId) { %> +
  • Message ID: <%= messageId %>
  • + <% } %> + <% if(contentType) { %> +
  • + Content type: <%- `${contentType}` %> +
  • + <% } %> + <% if(correlationId) { %> + + <% } %> +
    + <% } %> + <% if(message.hasDescription()) { %> + <%- md.render(message.description()) %> + <% } %> + + <% if(externalDocs) { %> + + <%- md.render(externalDocs.description() || 'Find more info here.') %> + + <% } %> + + <% if(headers) { %> +
    Headers
    + <%- include(schemaPath,{schema:headers,schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> + <% const ex = messageHelper.getHeadersExamples(message); + if (ex) { %> +
    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) { %> +
    Payload
    + <%- include(schemaPath,{schema:payload, schemaName:"", hideTitle:true, schemaHelper, md, path:""}) %> + <% const examples = messageHelper.getPayloadExamples(message); + if (examples) { %> +
    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) { %>

    Parameters

    - <%- include(schemaPath,{}) %> + <%- 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 }) %> - <%- include(bindingsPath,{ name:"Operation specific information", bindings: operation.bindings(), schemaHelper, schemaPath }) %> - <%- include(extensionsPath,{ name:"Channel extensions", extensions: channel.extensions(), schemaHelper, schemaPath }) %> - <%- include(extensionsPath,{ name:"Operation extensions", extensions: operation.extensions(), schemaHelper, schemaPath }) %> + <%- 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) { %> -

    <%= 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 ) { %> +

    <%= schemaName %>

    +<% } %> + + + + + + + + + + + + + <% if ( schema && !(schemaName.indexOf('x-parser-')> -1 || schemaName.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema, schemaName, 'root'); %> + + + + + + + + + <% const dependentSchemas = schemaHelper.getDependentSchemas(schema); + const extensions = schemaHelper.getCustomExtensions(schema); + const extensionsSchema = (extensions || Object.keys(extensions).length)? schemaHelper.jsonToSchema(extensions) : null; + const properties = schema.properties() || {}; + if (Object.keys(properties)) { + const required = schema.required() || []; + const patternProperties = schema.patternProperties() || {}; + for(let [propertyName, property] of Object.entries(properties) ) { %> + <% if ( property && !(propertyName.indexOf('x-parser-')> -1 || propertyName.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(property, propertyName, required.includes(propertyName), schemaHelper.getDependentRequired(propertyName,schema), buildPath((path || schemaName), propertyName)); %> + + + + + + + + + <% } %> + <% } %> + <% for(let [propertyName, property] of Object.entries(patternProperties) ) { %> + <% if ( property && !(propertyName.indexOf('x-parser-')> -1 || propertyName.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(property, propertyName, false, [], buildPath((path || schemaName), propertyName), 'pattern property'); %> + + + + + + + + + <% } %> + <% } %> + + <% const type = schema.type(); + const types = Array.isArray(type) ? type : [type]; + if (!(type !== undefined && !types.includes('array'))) { + const items = schema.items(); + if (items && !Array.isArray(items) && Object.keys(items.properties() || {}).length) { + const properties = items.properties() || {}; + if (Object.keys(properties)) { + const required = items.required() || []; + const patternProperties = items.patternProperties() || {}; + for(let [propertyName, property] of Object.entries(properties) ) { %> + <% if ( property && !(propertyName.indexOf('x-parser-')> -1 || propertyName.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(property, propertyName, required.includes(propertyName), schemaHelper.getDependentRequired(propertyName,items), buildPath((path || ""), propertyName)); %> + + + + + + + + + <% } %> + <% } %> + <% for(let [propertyName, property] of Object.entries(patternProperties) ) { %> + <% if ( property && !(propertyName.indexOf('x-parser-')> -1 || propertyName.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(property, propertyName, false, [], path, 'Single Item'); %> + + + + + + + + + <% } %> + <% } %> + <% } %> + <% } else if (Array.isArray(items)) { %> + + <% items.map((item, idx) => { + if ( item ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(item, "", false, [], buildPath(path || "", idx), 'index'); %> + + + + + + + + + <% } %> + <% }) %> + + <% }else { %> + <% if ( items ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(items, "", false, [], path, 'Single Item'); %> + + + + + + + + + <% } %> + <% } %> + + <% } %> + + <% } %> + <% schema.oneOf() && schema.oneOf().map((s, idx) => { + if ( s && !(idx.indexOf('x-parser-')> -1 || idx.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(s, idx, false, [], buildPath(path || schemaName, idx), 'oneOf item'); %> + + + + + + + + + <% } %> + <% }) %> + <% schema.anyOf() && schema.anyOf().map((s, idx) => { + if ( s && !(idx.indexOf('x-parser-')> -1 || idx.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(s, idx, false, [], buildPath(path || schemaName, idx), 'anyOf item'); %> + + + + + + + + + <% } %> + <% }) %> + <% schema.allOf() && schema.allOf().map((s, idx) => { + if ( s && !(idx.indexOf('x-parser-')> -1 || idx.indexOf('x-schema-private-') > -1) ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(s, idx, false, [], buildPath(path || schemaName, idx), 'allOf item'); %> + + + + + + + + + <% } %> + <% }) %> + <% if ( schema.not() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.not(), "", false, [], path,'not', false); %> + + + + + + + + + <% } %> + <% if ( schema.propertyNames() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.propertyNames(), "", false, [], path,'property names', false); %> + + + + + + + + + <% } %> + <% if ( schema.contains() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.contains(), "", false, [], path,'contains', false); %> + + + + + + + + + <% } %> + <% if ( schema.if() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.if(), "", false, [], path,'if', false); %> + + + + + + + + + <% } %> + <% if ( schema.then() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.then(), "", false, [], path,'then', false); %> + + + + + + + + + <% } %> + <% if ( schema.else() ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(schema.else(), "", false, [], path,'else', false); %> + + + + + + + + + <% } %> + <% if ( dependentSchemas ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(dependentSchemas, "", false, [], path,'dependant schemas'); %> + + + + + + + + + <% } %> + <% if ( extensionsSchema ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(extensionsSchema, "", false, [], path); %> + + + + + + + + + <% } %> + <% const ext = schema.extensions(); + if (ext.get('x-schema-private-render-additional-info')?.value() === true) { + const type = schema.type(); + const types = Array.isArray(type) ? type : [type]; + if (!(type !== undefined && !types.includes('object'))) { + const additionalProperties = schema.additionalProperties(); + if (!(additionalProperties === true || additionalProperties === undefined || additionalProperties === false)) { + if ( additionalProperties ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(additionalProperties, "", false, [], path,'additional properties', false); %> + + + + + + + + + <% } %> + <% } %> + <% } %> + <% } %> + <% if (ext.get('x-schema-private-render-additional-info')?.value() === true) { + const type = schema.type(); + const types = Array.isArray(type) ? type : [type]; + if (!(type !== undefined && !types.includes('array'))) { + if (Array.isArray(schema.items())) { + const additionalItems = schema.additionalItems(); + if (!(additionalItems === true || additionalItems === undefined || additionalItems === false)) { + if ( additionalItems ) { + const [renderedName, schemaType, description, values, constraints, notes] = schemaPropRow(additionalItems, "", false, [], path,'additional items', false); %> + + + + + + + + + <% } %> + <% } %> + <% } %> + <% } %> + <% } %> + +<% } %> + +
    NameTypeDescriptionValueConstraintsNotes
    <%- (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 || '-') %>
    + +<% function schemaPropRow( schema, schemaName, required = false, dependentRequired = [], path = '', nameNote = '',tryRenderAdditionalNotes = true) { + const isCircular = schema.isCircular() || false; + const extensions = schema.extensions(); + const renderType = extensions.get('x-schema-private-render-type')?.value() !== false; + const rawValue = extensions.get('x-schema-private-raw-value')?.value() === true; + const name = tree(path) || schemaName; + const schemaType = renderType && schemaHelper.toSchemaType(schema); + let description = (schema.description() || '').replace(new RegExp('\S*\r?\n','g'), ' '); + const externalDocs = schema.externalDocs(); + description = externalDocs ? `${!description.endsWith('.') ? `${description}.` : description} [${externalDocs.description() || 'Documentation'}](${externalDocs.url()})` : description; + description = description.trim(); + const values = rawValue ? `\`${schemaHelper.prettifyValue(schema.const())}\`` : schemaValues(schema); + const constraints = schemaConstraints(schema); + const notes = schemaNotes({ schema, required, dependentRequired, isCircular, tryRenderAdditionalNotes }); + + let renderedName = ''; + if (nameNote) { + renderedName = name ? `${name} (${nameNote})` : `(${nameNote})`; + } else { + renderedName = name; + } + return [renderedName, schemaType, description, values, constraints, notes]; +} + +function tree(path = '') { + path = String(path); + const filteredPaths = path.split('.').filter(Boolean); + return filteredPaths.join('.'); +} + +function buildPath(path = '', field = '') { + console.log(path,field); + if (!path) return field; + return `${path}.${field}`; +} + +function schemaValues(schema) { + if (!schema) return null; + const values = []; + + if (schema.default()) values.push(`default (\`${schemaHelper.prettifyValue(schema.default())}\`)`); + if (schema.const()) values.push(`const (\`${schemaHelper.prettifyValue(schema.const())}\`)`); + if (schema.enum()) { + const allowed = schema.enum().map(v => `\`${schemaHelper.prettifyValue(v)}\``).join(', '); + values.push(`allowed (${allowed})`); + } + if (schema.examples()) { + const examples = schema.examples().map(v => `\`${schemaHelper.prettifyValue(v)}\``).join(', '); + values.push(`examples (${examples})`); + } + + return values.join(', '); +} + +function schemaConstraints(schema) { + if (!schema) return null; + const constraints = []; + + if (schema.format()) constraints.push(`format (\`${schema.format()}\`)`); + if (schema.pattern()) constraints.push(`pattern (\`${schema.pattern()}\`)`); + if (schema.contentMediaType()) constraints.push(`media type (\`${schema.contentMediaType()}\`)`); + if (schema.contentEncoding()) constraints.push(`encoding (\`${schema.contentEncoding()}\`)`); + + return constraints.concat(schemaHelper.humanizeConstraints(schema)).join(', '); +} + +function schemaNotes({ schema, required = false, dependentRequired = [], isCircular = false, tryRenderAdditionalNotes }) { + if (!schema) return null; + const notes = []; + + if (schema.deprecated()) notes.push('deprecated'); + + if (required) notes.push('required'); + if (dependentRequired.length) { + const deps = dependentRequired.map(v => `\`${v}\``).join(', '); + notes.push(`required when defined (${deps})`); + } + const extensions = schema.extensions(); + const parameterLocation = extensions.get('x-schema-private-parameter-location'); + if (parameterLocation?.value()) { + notes.push(`parameter location (${parameterLocation.value()})`); + } + + if (isCircular) notes.push('circular'); + if (schema.writeOnly()) notes.push('write-only'); + if (schema.readOnly()) notes.push('read-only'); + if (extensions.get('x-schema-private-render-additional-info')?.value() !== false) { + const type = schema.type(); + const types = Array.isArray(type) ? type : [type]; + if ( + (type === undefined && tryRenderAdditionalNotes) || + types.includes('object') + ) { + const additionalProperties = schema.additionalProperties(); + if (additionalProperties === true || additionalProperties === undefined) { + notes.push('additional properties are allowed'); + } else if (additionalProperties === false) { + notes.push('additional properties are NOT allowed'); + } + } + if ( + ( + (type === undefined && tryRenderAdditionalNotes) || + types.includes('array') + ) && + Array.isArray(schema.items()) + ) { + const additionalItems = schema.additionalItems(); + if (additionalItems === true || additionalItems === undefined) { + notes.push('additional items are allowed'); + } else if (additionalItems === false) { + notes.push('additional items are NOT allowed'); + } + } + } + return notes.join(', '); +} %> \ No newline at end of file