UNPKG

vitepress-openapi

Version:

Generate VitePress API Documentation from OpenAPI Specification.

282 lines (241 loc) 8.11 kB
import type { OpenAPI } from '@scalar/openapi-types' import { literalTypes } from '../../index' import { getPropertyExamples } from '../examples/getPropertyExamples' import { getConstraints, hasConstraints } from './constraintsParser' import { resolveCircularRef } from './resolveCircularRef' type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null' interface Metadata { isCircularReference?: boolean isAdditionalProperties?: boolean isOneOf?: boolean isOneOfItem?: boolean isConstant?: boolean extra?: Record<string, unknown> } interface DocumentationReference { url?: string description?: string } export interface OAProperty { name: string types: JSONSchemaType[] required: boolean examples?: unknown[] title?: string description?: string defaultValue?: unknown docs?: DocumentationReference constraints?: Record<string, unknown> properties?: OAProperty[] enum?: unknown[] subtype?: JSONSchemaType subexamples?: unknown[] nullable?: boolean meta?: Metadata } class UiPropertyFactory { static createBaseProperty( name: string, property: Partial<OpenAPI.SchemaObject> = {}, required = false, ): OAProperty { const nodeTypes = Array.isArray(property.type) ? property.type : [property.type || 'string'] as JSONSchemaType[] const examples = getPropertyExamples(property) const baseProperty: OAProperty = { name, types: nodeTypes, required, ...(property.title && { title: property.title }), ...(property.description && { description: property.description }), ...(property.default !== undefined && { defaultValue: property.default }), ...(property.externalDocs && { docs: property.externalDocs }), ...(examples && { examples }), ...(property.nullable && { nullable: property.nullable }), } if (property.const !== undefined) { baseProperty.meta = { ...(baseProperty.meta || {}), isConstant: true } } if (hasConstraints(property)) { baseProperty.constraints = getConstraints(property) } Object.keys(property).forEach((key) => { if (key.startsWith('x-')) { baseProperty.meta = baseProperty.meta || {} baseProperty.meta.extra = baseProperty.meta.extra || {} baseProperty.meta.extra[key] = property[key] } }) return baseProperty } static createCircularReferenceProperty(name: string, circularRef: string): OAProperty { return { name, types: ['object'], required: false, description: `Circular reference to **${circularRef}**`, meta: { isCircularReference: true }, } } static createOneOfProperty(oneOfProperties: Partial<OpenAPI.SchemaObject>[], name: string = ''): OAProperty { return { name, types: ['object'], required: false, properties: oneOfProperties.map((prop) => { const property = UiPropertyFactory.schemaToUiProperty('', prop) property.meta = { ...(property.meta || {}), isOneOfItem: true } return property }), meta: { isOneOf: true }, } } static schemaToUiProperty( name: string, schema: Partial<OpenAPI.SchemaObject>, required = false, ): OAProperty { if (!schema || Object.keys(schema).length === 0) { return { name, types: [], required, } } if (schema.circularReference) { return UiPropertyFactory.createCircularReferenceProperty(name, schema.circularReference) } if (schema.oneOf) { return UiPropertyFactory.createOneOfProperty(schema.oneOf, name) } if (schema.const !== undefined) { const example = getPropertyExamples(schema) || schema.const return { name, types: [schema.type as JSONSchemaType || 'string'], required: false, examples: [example], meta: { isConstant: true }, } } if (literalTypes.includes(String(schema.type)) && schema.enum) { return { name, types: [schema.type as JSONSchemaType], required: false, enum: schema.enum, description: schema.description, } } const property = UiPropertyFactory.createBaseProperty(name, schema, required) switch (schema.type) { case 'array': if (schema.items) { const schemaType = determineSchemaType(schema.items) property.properties = schemaType === 'object' ? UiPropertyFactory.extractProperties( schema.items.properties, schema.items.required || [], schema.items.additionalProperties, ) : undefined if (schemaType !== undefined) { property.subtype = schemaType as JSONSchemaType } const itemsExamples = getPropertyExamples(schema.items) if (itemsExamples) { property.subexamples = itemsExamples } if (schema.items.const !== undefined) { property.meta = { ...(property.meta || {}), isConstant: true } } if (schema.items.oneOf) { property.meta = { ...(property.meta || {}), isOneOf: true } property.properties = schema.items.oneOf.map((prop: any) => { const propSchema = { ...prop, type: schema.items.type } return { ...UiPropertyFactory.schemaToUiProperty('', propSchema), meta: { ...(prop.meta || {}), isOneOfItem: true }, } }) } } break case 'object': property.properties = UiPropertyFactory.extractProperties( schema.properties, schema.required || [], schema.additionalProperties, ) break case undefined: if (schema.properties || schema.additionalProperties) { property.types = ['object'] property.properties = UiPropertyFactory.extractProperties( schema.properties, schema.required || [], schema.additionalProperties, ) } break } return property } static extractProperties( propertiesNode?: Record<string, OpenAPI.SchemaObject>, requiredProperties: string[] = [], additionalPropertiesNode?: OpenAPI.SchemaObject | boolean, ): OAProperty[] { const properties: OAProperty[] = [] if (propertiesNode) { Object.entries(propertiesNode).forEach(([key, value]) => { const isRequired = requiredProperties.includes(key) properties.push(UiPropertyFactory.schemaToUiProperty(key, value, isRequired)) }) } if (additionalPropertiesNode) { const additionalProps = typeof additionalPropertiesNode === 'object' ? additionalPropertiesNode : { type: 'string' } properties.push({ name: 'additionalProperties', types: [additionalProps.type as JSONSchemaType], required: false, meta: { isAdditionalProperties: true }, }) } return properties } } export function getSchemaUi(jsonSchema: OpenAPI.SchemaObject): OAProperty | OAProperty[] { if (!jsonSchema || Object.keys(jsonSchema).length === 0) { return [] } const resolvedSchema = resolveCircularRef(jsonSchema) return UiPropertyFactory.schemaToUiProperty('', resolvedSchema) } function determineSchemaType(schema: OpenAPI.SchemaObject): JSONSchemaType { if (!schema.type && schema.properties) { return 'object' } if (!schema.type && schema.items) { return 'array' } if (!schema.type && schema.const !== undefined) { if (Array.isArray(schema.const)) { return 'array' } else if (typeof schema.const === 'object' && schema.const !== null) { return 'object' } else if (typeof schema.const === 'string') { return 'string' } else if (typeof schema.const === 'number') { return 'number' } else if (typeof schema.const === 'boolean') { return 'boolean' } else { return 'null' } } return schema.type as JSONSchemaType }