diff --git a/docs/src/components/api/enhanced-api-page.tsx b/docs/src/components/api/enhanced-api-page.tsx index f844cfdf4e..6aec3d1760 100644 --- a/docs/src/components/api/enhanced-api-page.tsx +++ b/docs/src/components/api/enhanced-api-page.tsx @@ -1,12 +1,13 @@ 'use client'; -import { ArrowRight, Check, Code, Copy, Play, Send, Settings, Sparkles, Zap } from 'lucide-react'; +import { ArrowRight, Check, Code, Copy, Play, Send, Settings, Zap } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; +import { resolveSchema } from '../../lib/openapi-utils'; import { useAPIPageContext } from './api-page-wrapper'; import { Button } from './button'; // Types for OpenAPI specification -type OpenAPISchema = { +export type OpenAPISchema = { type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null', properties?: Record, items?: OpenAPISchema, @@ -27,7 +28,7 @@ type OpenAPISchema = { additionalProperties?: boolean | OpenAPISchema, } -type OpenAPISpec = { +export type OpenAPISpec = { openapi: string, info: { title: string, @@ -88,7 +89,7 @@ type EnhancedAPIPageProps = { type RequestState = { parameters: Record, headers: Record, - body: string, + bodyFields: Record, // Changed from 'body: string' to individual fields response: { status?: number, data?: unknown, @@ -124,7 +125,7 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA const [requestState, setRequestState] = useState({ parameters: {}, headers: {}, - body: '{}', + bodyFields: {}, // Changed from body: '{}' response: { loading: false, }, @@ -181,8 +182,8 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA if (prop.example !== undefined) { example[key] = prop.example; } else { - // Just use the field name as the value - example[key] = key; + // Use OpenAPI type information to show type instead of generated values + example[key] = `<${prop.type || 'unknown'}>`; } }); @@ -209,7 +210,7 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA return ""; }, []); - // Auto-populate request body based on OpenAPI schema + // Auto-populate request body fields based on OpenAPI schema useEffect(() => { if (operations.length > 0 && spec) { const firstOperation = operations[0]; @@ -217,16 +218,21 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA if (operation.requestBody?.content['application/json']?.schema) { const { schema: jsonSchema } = operation.requestBody.content['application/json']; - //console.log('OpenAPI Schema for', firstOperation.path, ':', jsonSchema); - const exampleBody = generateExampleFromSchema(jsonSchema, spec); - //console.log('Generated example body:', exampleBody); - setRequestState(prev => ({ - ...prev, - body: JSON.stringify(exampleBody, null, 2) - })); + // Initialize body fields from schema properties + if (jsonSchema.type === 'object' && jsonSchema.properties) { + const initialFields: Record = {}; + Object.entries(jsonSchema.properties).forEach(([key, prop]: [string, OpenAPISchema]) => { + if (prop.example !== undefined) { + initialFields[key] = prop.example; + } else { + initialFields[key] = ''; + } + }); + setRequestState(prev => ({ ...prev, bodyFields: initialFields })); + } } } - }, [spec, operations, generateExampleFromSchema]); + }, [spec, operations]); // Load OpenAPI specification useEffect(() => { @@ -303,8 +309,15 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA headers: filteredHeaders, }; - if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && requestState.body) { - requestOptions.body = requestState.body; + // Build request body from individual fields + if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && Object.keys(requestState.bodyFields).length > 0) { + // Filter out empty values and build JSON body + const bodyData = Object.fromEntries( + Object.entries(requestState.bodyFields).filter(([, value]) => value !== '' && value !== undefined) + ); + if (Object.keys(bodyData).length > 0) { + requestOptions.body = JSON.stringify(bodyData); + } } const response = await fetch(url, requestOptions); @@ -343,7 +356,7 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA } })); } - }, [spec, requestState.parameters, requestState.headers, requestState.body, reportError]); + }, [spec, requestState.parameters, requestState.headers, requestState.bodyFields, reportError]); // Changed from requestState.body if (loading) { return ( @@ -400,6 +413,7 @@ export function EnhancedAPIPage({ document, operations, description }: EnhancedA .catch(error => console.error('Failed to copy to clipboard:', error)); }} description={description || operation.description} + generateExample={generateExampleFromSchema} /> ); })} @@ -428,6 +442,7 @@ function ModernAPIPlayground({ onExecute: () => void, onCopy: (text: string) => void, description?: string, + generateExample: (schema: OpenAPISchema, spec?: OpenAPISpec) => unknown, }) { const [copied, setCopied] = useState(false); const [activeCodeTab, setActiveCodeTab] = useState<'curl' | 'javascript' | 'python'>('curl'); @@ -473,9 +488,14 @@ function ModernAPIPlayground({ } }); - // Add body for POST/PUT/PATCH - if (['POST', 'PUT', 'PATCH'].includes(method) && requestState.body !== '{}') { - curlCommand += ` \\\n -d '${requestState.body}'`; + // Add body for POST/PUT/PATCH - build from fields + if (['POST', 'PUT', 'PATCH'].includes(method) && Object.keys(requestState.bodyFields).length > 0) { + const bodyData = Object.fromEntries( + Object.entries(requestState.bodyFields).filter(([, value]) => value !== '' && value !== undefined) + ); + if (Object.keys(bodyData).length > 0) { + curlCommand += ` \\\n -d '${JSON.stringify(bodyData)}'`; + } } return curlCommand; @@ -516,8 +536,14 @@ function ModernAPIPlayground({ jsCode += `,\n headers: ${JSON.stringify(headers, null, 4).replace(/^/gm, ' ')}`; } - if (['POST', 'PUT', 'PATCH'].includes(method) && requestState.body !== '{}') { - jsCode += `,\n body: ${requestState.body}`; + // Add body for POST/PUT/PATCH - build from fields + if (['POST', 'PUT', 'PATCH'].includes(method) && Object.keys(requestState.bodyFields).length > 0) { + const bodyData = Object.fromEntries( + Object.entries(requestState.bodyFields).filter(([, value]) => value !== '' && value !== undefined) + ); + if (Object.keys(bodyData).length > 0) { + jsCode += `,\n body: ${JSON.stringify(bodyData, null, 2)}`; + } } jsCode += `\n});\n\nconst data = await response.json();\nconsole.log(data);`; @@ -561,9 +587,17 @@ function ModernAPIPlayground({ pythonCode += `headers = ${JSON.stringify(headers, null, 2).replace(/"/g, "'")}\n`; } - if (['POST', 'PUT', 'PATCH'].includes(method) && requestState.body !== '{}') { - pythonCode += `data = ${requestState.body}\n\n`; - pythonCode += `response = requests.${method.toLowerCase()}(url${Object.keys(headers).length > 0 ? ', headers=headers' : ''}${requestState.body !== '{}' ? ', json=data' : ''})\n`; + // Add body for POST/PUT/PATCH - build from fields + if (['POST', 'PUT', 'PATCH'].includes(method) && Object.keys(requestState.bodyFields).length > 0) { + const bodyData = Object.fromEntries( + Object.entries(requestState.bodyFields).filter(([, value]) => value !== '' && value !== undefined) + ); + if (Object.keys(bodyData).length > 0) { + pythonCode += `data = ${JSON.stringify(bodyData)}\n\n`; + pythonCode += `response = requests.${method.toLowerCase()}(url${Object.keys(headers).length > 0 ? ', headers=headers' : ''}, json=data)\n`; + } else { + pythonCode += `\nresponse = requests.${method.toLowerCase()}(url${Object.keys(headers).length > 0 ? ', headers=headers' : ''})\n`; + } } else { pythonCode += `\nresponse = requests.${method.toLowerCase()}(url${Object.keys(headers).length > 0 ? ', headers=headers' : ''})\n`; } @@ -671,19 +705,24 @@ function ModernAPIPlayground({ /> )} - {/* Request Body */} + {/* Request Body Fields */} {operation.requestBody && ( - setRequestState(prev => ({ ...prev, body }))} + spec={spec} + values={requestState.bodyFields} + onChange={(bodyFields) => setRequestState(prev => ({ ...prev, bodyFields }))} /> )} {/* Response Panel */} - + {/* Code Examples */}
@@ -813,141 +852,475 @@ function ParametersSection({ ); } -// Request Body Section - Clean design -function RequestBodySection({ - requestBody, - value, - onChange, +// Response Schema Section - styled like parameters but read-only +function ResponseSchemaSection({ + operation, + spec, }: { - requestBody: OpenAPIOperation['requestBody'], - value: string, - onChange: (value: string) => void, + operation: OpenAPIOperation, + spec: OpenAPISpec, }) { - if (!requestBody) return null; - return ( -
-
Request Body
-