UNPKG

fortify-schema

Version:

TypeScript interface-like schema validation system that's easier to use than Zod

339 lines (299 loc) 9.43 kB
/** * OpenAPI Converter - Convert schemas to OpenAPI specifications * * This module provides utilities to convert Fortify Schema definitions * to OpenAPI 3.0 specifications for API documentation. */ import { SchemaInterface } from "../../mode/interfaces/Interface"; /** * OpenAPI converter for schema definitions */ export class OpenAPIConverter { /** * Convert a schema to OpenAPI format */ static convertSchema(schema: SchemaInterface): OpenAPISchemaObject { const properties: Record<string, any> = {}; const required: string[] = []; Object.entries(schema).forEach(([fieldName, fieldType]) => { const openApiProperty = this.convertField(fieldType); properties[fieldName] = openApiProperty.schema; if (openApiProperty.required) { required.push(fieldName); } }); return { type: "object", properties, required: required.length > 0 ? required : undefined }; } /** * Convert a single field to OpenAPI property */ private static convertField(fieldType: any): { schema: any; required: boolean } { if (typeof fieldType === "string") { return this.convertStringField(fieldType); } if (typeof fieldType === "object" && !Array.isArray(fieldType)) { return this.convertObjectField(fieldType); } // Fallback return { schema: { type: "string" }, required: true }; } /** * Convert string-based field definitions */ private static convertStringField(fieldType: string): { schema: any; required: boolean } { const isOptional = fieldType.includes("?"); const cleanType = fieldType.replace("?", ""); // Handle string types with constraints if (cleanType.startsWith("string")) { const constraints = this.parseStringConstraints(cleanType); return { schema: { type: "string", ...constraints }, required: !isOptional }; } // Handle specific string formats const formatMappings: Record<string, any> = { email: { type: "string", format: "email" }, url: { type: "string", format: "uri" }, uuid: { type: "string", format: "uuid" }, date: { type: "string", format: "date-time" }, phone: { type: "string", pattern: "^\\+?[1-9]\\d{1,14}$" } }; if (formatMappings[cleanType]) { return { schema: formatMappings[cleanType], required: !isOptional }; } // Handle number types if (cleanType === "number") { return { schema: { type: "number" }, required: !isOptional }; } if (cleanType === "positive") { return { schema: { type: "number", minimum: 0, exclusiveMinimum: true }, required: !isOptional }; } if (cleanType === "int") { return { schema: { type: "integer" }, required: !isOptional }; } // Handle boolean if (cleanType === "boolean") { return { schema: { type: "boolean" }, required: !isOptional }; } // Handle arrays if (cleanType.includes("[]")) { const itemType = cleanType.replace("[]", ""); const itemSchema = this.convertField(itemType).schema; return { schema: { type: "array", items: itemSchema }, required: !isOptional }; } // Default to string return { schema: { type: "string" }, required: !isOptional }; } /** * Convert object field definitions */ private static convertObjectField(fieldType: any): { schema: any; required: boolean } { const nestedProperties: Record<string, any> = {}; const nestedRequired: string[] = []; Object.entries(fieldType).forEach(([nestedField, nestedType]) => { const nestedProperty = this.convertField(nestedType); nestedProperties[nestedField] = nestedProperty.schema; if (nestedProperty.required) { nestedRequired.push(nestedField); } }); return { schema: { type: "object", properties: nestedProperties, required: nestedRequired.length > 0 ? nestedRequired : undefined }, required: true }; } /** * Parse string constraints from string(min,max) format */ private static parseStringConstraints(stringType: string): any { const constraints: any = {}; const match = stringType.match(/string\((\d+)?,?(\d+)?\)/); if (match) { if (match[1]) constraints.minLength = parseInt(match[1]); if (match[2]) constraints.maxLength = parseInt(match[2]); } return constraints; } /** * Generate complete OpenAPI specification */ static generateOpenAPISpec( schema: SchemaInterface, options: OpenAPISpecOptions ): OpenAPISpecification { const schemaObject = this.convertSchema(schema); return { openapi: "3.0.0", info: { title: options.title, version: options.version, description: options.description }, servers: options.servers?.map(url => ({ url })) || [], components: { schemas: { [options.schemaName || options.title]: schemaObject } }, paths: options.paths || {} }; } /** * Generate schema reference for use in OpenAPI paths */ static generateSchemaReference(schemaName: string): OpenAPIReference { return { $ref: `#/components/schemas/${schemaName}` }; } /** * Generate request body specification */ static generateRequestBody( schema: SchemaInterface, options: RequestBodyOptions = {} ): OpenAPIRequestBody { const schemaObject = this.convertSchema(schema); return { description: options.description || "Request body", required: options.required !== false, content: { "application/json": { schema: schemaObject, examples: options.examples } } }; } /** * Generate response specification */ static generateResponse( schema: SchemaInterface, options: ResponseOptions = {} ): OpenAPIResponse { const schemaObject = this.convertSchema(schema); return { description: options.description || "Successful response", content: { "application/json": { schema: schemaObject, examples: options.examples } }, headers: options.headers }; } } /** * Type definitions */ export interface OpenAPISchemaObject { type: string; properties?: Record<string, any>; required?: string[]; items?: any; format?: string; pattern?: string; minimum?: number; maximum?: number; exclusiveMinimum?: boolean; exclusiveMaximum?: boolean; minLength?: number; maxLength?: number; minItems?: number; maxItems?: number; } export interface OpenAPISpecOptions { title: string; version: string; description?: string; schemaName?: string; servers?: string[]; paths?: Record<string, any>; } export interface OpenAPISpecification { openapi: string; info: { title: string; version: string; description?: string; }; servers?: Array<{ url: string }>; components: { schemas: Record<string, OpenAPISchemaObject>; }; paths: Record<string, any>; } export interface OpenAPIReference { $ref: string; } export interface RequestBodyOptions { description?: string; required?: boolean; examples?: Record<string, any>; } export interface OpenAPIRequestBody { description: string; required: boolean; content: Record<string, { schema: OpenAPISchemaObject; examples?: Record<string, any>; }>; } export interface ResponseOptions { description?: string; examples?: Record<string, any>; headers?: Record<string, any>; } export interface OpenAPIResponse { description: string; content: Record<string, { schema: OpenAPISchemaObject; examples?: Record<string, any>; }>; headers?: Record<string, any>; }