import {openapi} from '@/lib/openapi'; interface SchemaLike { type?: string | string[]; format?: string; enum?: unknown[]; description?: string; default?: unknown; example?: unknown; required?: string[]; properties?: Record; items?: SchemaLike; oneOf?: SchemaLike[]; anyOf?: SchemaLike[]; allOf?: SchemaLike[]; nullable?: boolean; [key: string]: unknown; } interface Parameter { name: string; in: string; required?: boolean; description?: string; schema?: SchemaLike; } interface Operation { operationId?: string; summary?: string; description?: string; deprecated?: boolean; tags?: string[]; parameters?: Parameter[]; requestBody?: { required?: boolean; description?: string; content?: Record; }; responses?: Record< string, { description?: string; content?: Record; } >; security?: Array>; } function typeLabel(schema?: SchemaLike): string { if (!schema) return 'unknown'; if (schema.enum) return `enum (${schema.enum.map(v => JSON.stringify(v)).join(' | ')})`; if (schema.oneOf) return schema.oneOf.map(typeLabel).join(' | '); if (schema.anyOf) return schema.anyOf.map(typeLabel).join(' | '); if (schema.allOf) return schema.allOf.map(typeLabel).join(' & '); if (Array.isArray(schema.type)) return schema.type.join(' | '); const base = schema.type ?? 'object'; if (base === 'array' && schema.items) return `array<${typeLabel(schema.items)}>`; if (schema.format) return `${base} (${schema.format})`; return base; } function renderSchemaTree(schema: SchemaLike | undefined, depth = 0, lines: string[] = []): string[] { if (!schema) return lines; const indent = ' '.repeat(depth); if (schema.allOf) { for (const sub of schema.allOf) renderSchemaTree(sub, depth, lines); return lines; } if (schema.type === 'object' || schema.properties) { const required = new Set(schema.required ?? []); const props = schema.properties ?? {}; for (const [name, prop] of Object.entries(props)) { const tag = required.has(name) ? ' (required)' : ''; const desc = prop.description ? ` — ${prop.description.replace(/\n+/g, ' ')}` : ''; lines.push(`${indent}- \`${name}\`: ${typeLabel(prop)}${tag}${desc}`); if (prop.type === 'object' || prop.properties) { renderSchemaTree(prop, depth + 1, lines); } else if (prop.type === 'array' && prop.items && (prop.items.type === 'object' || prop.items.properties)) { lines.push(`${indent} items:`); renderSchemaTree(prop.items, depth + 2, lines); } } return lines; } if (schema.type === 'array' && schema.items) { lines.push(`${indent}- items: ${typeLabel(schema.items)}`); renderSchemaTree(schema.items, depth + 1, lines); } return lines; } function exampleFromSchema(schema?: SchemaLike): unknown { if (!schema) return undefined; if (schema.example !== undefined) return schema.example; if (schema.default !== undefined) return schema.default; if (schema.enum && schema.enum.length > 0) return schema.enum[0]; if (schema.allOf) { const merged: Record = {}; for (const sub of schema.allOf) Object.assign(merged, exampleFromSchema(sub) as object ?? {}); return merged; } if (schema.oneOf?.[0]) return exampleFromSchema(schema.oneOf[0]); if (schema.anyOf?.[0]) return exampleFromSchema(schema.anyOf[0]); if (schema.properties) { const out: Record = {}; const required = new Set(schema.required ?? Object.keys(schema.properties)); for (const [name, prop] of Object.entries(schema.properties)) { if (!required.has(name) && schema.required) continue; out[name] = exampleFromSchema(prop); } return out; } if (schema.type === 'array') { const item = exampleFromSchema(schema.items); return item === undefined ? [] : [item]; } switch (schema.type) { case 'string': if (schema.format === 'email') return 'user@example.com'; if (schema.format === 'date-time') return new Date().toISOString(); if (schema.format === 'uri' || schema.format === 'url') return 'https://example.com'; return 'string'; case 'integer': case 'number': return 0; case 'boolean': return false; default: return null; } } function findOperationByMethodPath( doc: {paths?: Record>}, method: string, path: string, ): Operation | undefined { const pathItem = doc.paths?.[path]; return pathItem?.[method.toLowerCase()]; } function extractOperationRef(mdxBody: string): {method: string; path: string} | undefined { const block = mdxBody.match(/operations=\{(\[[\s\S]*?\])\}/); if (!block) return undefined; const pathMatch = block[1].match(/['"]?path['"]?\s*:\s*['"]([^'"]+)['"]/); const methodMatch = block[1].match(/['"]?method['"]?\s*:\s*['"]([^'"]+)['"]/); if (!pathMatch || !methodMatch) return undefined; return {path: pathMatch[1], method: methodMatch[1]}; } export async function renderOpenAPIOperationFromMDX( mdxBody: string, fallbackTitle: string, pageUrl: string, ): Promise { const ref = extractOperationRef(mdxBody); if (!ref) return undefined; const schemas = await openapi.getSchemas(); const first = Object.values(schemas)[0]; if (!first) return undefined; const doc = first.dereferenced as { servers?: Array<{url: string}>; paths?: Record>; }; const op = findOperationByMethodPath(doc, ref.method, ref.path); if (!op) return undefined; const lines: string[] = []; const method = ref.method.toUpperCase(); const baseUrl = doc.servers?.[0]?.url ?? ''; lines.push(`# ${op.summary ?? fallbackTitle} (${pageUrl})`); lines.push(''); lines.push(`\`${method} ${ref.path}\``); lines.push(''); if (baseUrl) { lines.push(`Base URL: \`${baseUrl}\``); lines.push(''); } if (op.description) { lines.push(op.description); lines.push(''); } if (op.deprecated) { lines.push('> **Deprecated.** This endpoint should not be used in new integrations.'); lines.push(''); } if (op.parameters && op.parameters.length > 0) { const grouped: Record = {}; for (const p of op.parameters) (grouped[p.in] ??= []).push(p); for (const [location, params] of Object.entries(grouped)) { lines.push(`## ${location[0].toUpperCase()}${location.slice(1)} parameters`); lines.push(''); for (const p of params) { const req = p.required ? ' (required)' : ''; const desc = p.description ? ` — ${p.description.replace(/\n+/g, ' ')}` : ''; lines.push(`- \`${p.name}\`: ${typeLabel(p.schema)}${req}${desc}`); } lines.push(''); } } const body = op.requestBody?.content?.['application/json']?.schema; if (body) { lines.push('## Request body'); lines.push(''); if (op.requestBody?.description) { lines.push(op.requestBody.description); lines.push(''); } const tree = renderSchemaTree(body); if (tree.length > 0) { lines.push(...tree); lines.push(''); } const example = exampleFromSchema(body); if (example !== undefined) { lines.push('Example:'); lines.push(''); lines.push('```json'); lines.push(JSON.stringify(example, null, 2)); lines.push('```'); lines.push(''); } } if (op.responses) { lines.push('## Responses'); lines.push(''); for (const [status, resp] of Object.entries(op.responses)) { const desc = resp.description ? ` — ${resp.description.replace(/\n+/g, ' ')}` : ''; lines.push(`### \`${status}\`${desc}`); lines.push(''); const schema = resp.content?.['application/json']?.schema; if (schema) { const tree = renderSchemaTree(schema); if (tree.length > 0) { lines.push(...tree); lines.push(''); } const example = exampleFromSchema(schema); if (example !== undefined) { lines.push('```json'); lines.push(JSON.stringify(example, null, 2)); lines.push('```'); lines.push(''); } } } } if (baseUrl) { lines.push('## Example request'); lines.push(''); lines.push('```bash'); const curlParts = [`curl -X ${method} '${baseUrl}${ref.path}'`, " -H 'Authorization: Bearer YOUR_API_KEY'"]; if (body) { curlParts.push(" -H 'Content-Type: application/json'"); const example = exampleFromSchema(body); curlParts.push(` -d '${JSON.stringify(example)}'`); } lines.push(curlParts.join(' \\\n')); lines.push('```'); lines.push(''); } return lines.join('\n'); }