Skip to main content
Glama
generateOpenApi.ts8.56 kB
/** * Generate OpenAPI 3.0 specification from analyzed PHP routes * * Converts route information from PHP analyzers into OpenAPI/Swagger documentation */ import { FileAnalysis, RouteInfo, MiddlewareInfo, RouteParameter } from '../core/types.js'; export interface OpenAPISpec { openapi: string; info: { title: string; version: string; description?: string; }; servers?: Array<{ url: string; description?: string; }>; paths: Record<string, any>; components?: { schemas?: Record<string, any>; securitySchemes?: Record<string, any>; }; security?: Array<Record<string, string[]>>; } /** * Generate OpenAPI specification from analyzed files */ export function generateOpenApiSpec( files: FileAnalysis[], options: { title?: string; version?: string; description?: string; serverUrl?: string; } = {} ): OpenAPISpec { const spec: OpenAPISpec = { openapi: '3.0.0', info: { title: options.title || 'API Documentation', version: options.version || '1.0.0', description: options.description || 'Auto-generated API documentation from code analysis', }, paths: {}, components: { securitySchemes: {}, schemas: {}, }, }; if (options.serverUrl) { spec.servers = [ { url: options.serverUrl, description: 'API Server', }, ]; } // Collect all security schemes from middleware const securitySchemes = new Set<string>(); // Process each file for (const file of files) { if (!file.classes) continue; for (const classInfo of file.classes) { if (!classInfo.routes || classInfo.routes.length === 0) continue; // Process each route for (const route of classInfo.routes) { const pathItem = convertRouteToOpenAPI(route, classInfo.name || 'Unknown', file.frameworkInfo?.name); // Add to paths if (!spec.paths[route.path]) { spec.paths[route.path] = {}; } // Add each HTTP method for (const httpMethod of route.httpMethods) { spec.paths[route.path][httpMethod.toLowerCase()] = pathItem; // Collect security schemes if (route.middleware) { for (const middleware of route.middleware) { securitySchemes.add(middleware.name); } } } } } } // Add security schemes spec.components!.securitySchemes = generateSecuritySchemes(Array.from(securitySchemes)); // Remove empty components if (Object.keys(spec.components!.securitySchemes || {}).length === 0) { delete spec.components!.securitySchemes; } if (Object.keys(spec.components!.schemas || {}).length === 0) { delete spec.components!.schemas; } if (Object.keys(spec.components || {}).length === 0) { delete spec.components; } return spec; } /** * Convert route to OpenAPI path item */ function convertRouteToOpenAPI( route: RouteInfo, className: string, framework?: string | null ): any { const operation: any = { summary: `${className}.${route.method}`, description: `Route: ${route.path}`, operationId: `${className}_${route.method}`, tags: [className], parameters: [], responses: { '200': { description: 'Successful response', content: { 'application/json': { schema: { type: 'object', }, }, }, }, '400': { description: 'Bad request', }, '401': { description: 'Unauthorized', }, '500': { description: 'Internal server error', }, }, }; // Add parameters for (const param of route.parameters) { operation.parameters.push(convertParameterToOpenAPI(param, route.path)); } // Add security from middleware if (route.middleware && route.middleware.length > 0) { operation.security = []; for (const middleware of route.middleware) { operation.security.push({ [middleware.name]: middleware.parameters, }); } } return operation; } /** * Convert route parameter to OpenAPI parameter */ function convertParameterToOpenAPI(param: RouteParameter, routePath: string): any { // Determine if parameter is in path or query const inPath = routePath.includes(`{${param.name}}`) || routePath.includes(`/${param.name}`); return { name: param.name, in: inPath ? 'path' : 'query', required: param.required && inPath, schema: { type: mapTypeToOpenAPI(param.type), }, description: param.defaultValue ? `Default: ${param.defaultValue}` : undefined, }; } /** * Map PHP type to OpenAPI type */ function mapTypeToOpenAPI(phpType: string | null): string { if (!phpType) return 'string'; const type = phpType.toLowerCase(); // Simple mappings if (type === 'int' || type === 'integer') return 'integer'; if (type === 'float' || type === 'double') return 'number'; if (type === 'bool' || type === 'boolean') return 'boolean'; if (type === 'array') return 'array'; if (type === 'object') return 'object'; // Union types - use first type if (type.includes('|')) { const firstType = type.split('|')[0].trim(); return mapTypeToOpenAPI(firstType); } // Default to string return 'string'; } /** * Generate security schemes from middleware names */ function generateSecuritySchemes(middlewareNames: string[]): Record<string, any> { const schemes: Record<string, any> = {}; for (const name of middlewareNames) { const nameLower = name.toLowerCase(); // OAuth2 patterns if (nameLower.includes('oauth')) { schemes[name] = { type: 'oauth2', flows: { implicit: { authorizationUrl: '/oauth/authorize', scopes: {}, }, }, }; } // JWT / Bearer token else if (nameLower === 'auth' || nameLower === 'jwt' || nameLower.includes('bearer')) { schemes[name] = { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', }; } // API Key else if (nameLower.includes('apikey') || nameLower.includes('api_key')) { schemes[name] = { type: 'apiKey', in: 'header', name: 'X-API-Key', }; } // Basic Auth else if (nameLower === 'basic') { schemes[name] = { type: 'http', scheme: 'basic', }; } // Default to API Key for other middleware else if (nameLower !== 'throttle' && !nameLower.includes('throttle') && nameLower !== 'csrf' && nameLower !== 'cors') { schemes[name] = { type: 'apiKey', in: 'header', name: `X-${name.charAt(0).toUpperCase() + name.slice(1)}`, }; } } return schemes; } /** * Export OpenAPI spec to JSON string */ export function exportOpenApiJson(spec: OpenAPISpec, pretty: boolean = true): string { return JSON.stringify(spec, null, pretty ? 2 : 0); } /** * Export OpenAPI spec to YAML string */ export function exportOpenApiYaml(spec: OpenAPISpec): string { // Simple YAML conversion (for production, use a proper YAML library) const yaml = convertToYaml(spec, 0); return yaml; } /** * Simple YAML converter (basic implementation) */ function convertToYaml(obj: any, indent: number = 0): string { const spaces = ' '.repeat(indent); let yaml = ''; if (Array.isArray(obj)) { for (const item of obj) { if (typeof item === 'object' && item !== null) { yaml += `${spaces}-\n${convertToYaml(item, indent + 1)}`; } else { yaml += `${spaces}- ${item}\n`; } } } else if (typeof obj === 'object' && obj !== null) { for (const [key, value] of Object.entries(obj)) { if (value === undefined) continue; if (typeof value === 'object' && value !== null) { if (Array.isArray(value) && value.length === 0) { yaml += `${spaces}${key}: []\n`; } else if (!Array.isArray(value) && Object.keys(value).length === 0) { yaml += `${spaces}${key}: {}\n`; } else { yaml += `${spaces}${key}:\n${convertToYaml(value, indent + 1)}`; } } else if (typeof value === 'string') { // Escape strings with special characters const escaped = value.includes(':') || value.includes('#') || value.includes('[') ? `"${value.replace(/"/g, '\\"')}"` : value; yaml += `${spaces}${key}: ${escaped}\n`; } else { yaml += `${spaces}${key}: ${value}\n`; } } } return yaml; }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LiL-Loco/documentation-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server