UNPKG

fumadocs-openapi

Version:

Generate MDX docs for your OpenAPI spec

128 lines (127 loc) 10.4 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import { Fragment } from 'react'; import { idToTitle } from '../../utils/id-to-title.js'; import { Markdown } from '../markdown.js'; import { heading } from '../heading.js'; import { Schema } from '../schema.js'; import { createMethod } from '../../server/create-method.js'; import { methodKeys } from '../../build-routes.js'; import { APIExample, APIExampleProvider, getAPIExamples, } from '../../render/operation/api-example.js'; import { MethodLabel } from '../../ui/components/method-label.js'; import { getTypescriptSchema } from '../../utils/get-typescript-schema.js'; import { CopyResponseTypeScript } from '../../ui/client.js'; import { SelectTab, SelectTabs, SelectTabTrigger } from '../../ui/select-tabs.js'; import { AccordionContent, AccordionHeader, AccordionItem, Accordions, AccordionTrigger, } from '../../ui/components/accordion.js'; const ParamTypes = { path: 'Path Parameters', query: 'Query Parameters', header: 'Header Parameters', cookie: 'Cookie Parameters', }; export function Operation({ type = 'operation', path, method, ctx, hasHead, headingLevel = 2, }) { const body = method.requestBody; let headNode = null; let bodyNode = null; let authNode = null; let responseNode = null; let callbacksNode = null; if (hasHead) { const title = method.summary ?? (method.operationId ? idToTitle(method.operationId) : path); headNode = (_jsxs(_Fragment, { children: [heading(headingLevel, title, ctx), method.description ? _jsx(Markdown, { text: method.description }) : null] })); headingLevel++; } const contentTypes = body ? Object.entries(body.content) : null; if (body && contentTypes && contentTypes.length > 0) { bodyNode = (_jsxs(SelectTabs, { defaultValue: contentTypes[0][0], children: [_jsxs("div", { className: "flex gap-2 items-end justify-between", children: [heading(headingLevel, 'Request Body', ctx), _jsx(SelectTabTrigger, { items: contentTypes.map((v) => v[0]), className: "mb-4" })] }), body.description && _jsx(Markdown, { text: body.description }), contentTypes.map(([type, content]) => { if (!(type in ctx.mediaAdapters)) { throw new Error(`Media type ${type} is not supported (in ${path})`); } return (_jsx(SelectTab, { value: type, children: _jsx(Schema, { name: "body", as: "body", schema: (content.schema ?? {}), required: body.required, readOnly: method.method === 'GET', writeOnly: method.method !== 'GET', ctx: ctx }) }, type)); })] })); } if (method.responses && ctx.showResponseSchema !== false) { const statuses = Object.keys(method.responses); responseNode = (_jsxs(_Fragment, { children: [heading(headingLevel, 'Response Body', ctx), _jsx(Accordions, { type: "multiple", children: statuses.map((status) => (_jsx(AccordionItem, { value: status, children: _jsx(ResponseAccordion, { status: status, operation: method, ctx: ctx }) }, status))) })] })); } const parameterNode = Object.entries(ParamTypes).map(([type, title]) => { const params = method.parameters?.filter((param) => param.in === type); if (!params || params.length === 0) return; return (_jsxs(Fragment, { children: [heading(headingLevel, title, ctx), _jsx("div", { className: "flex flex-col", children: params.map((param) => (_jsx(Schema, { name: param.name, schema: { ...param.schema, description: param.description ?? param.schema?.description, deprecated: (param.deprecated ?? false) || (param.schema?.deprecated ?? false), }, required: param.required, readOnly: method.method === 'GET', writeOnly: method.method !== 'GET', ctx: ctx }, param.name))) })] }, type)); }); const securities = (method.security ?? ctx.schema.document.security ?? []).filter((v) => Object.keys(v).length > 0); if (type === 'operation' && securities.length > 0) { const securitySchemes = ctx.schema.document.components?.securitySchemes; const names = securities.map((security) => Object.keys(security).join(' & ')); authNode = (_jsxs(SelectTabs, { defaultValue: names[0], children: [_jsxs("div", { className: "flex items-end justify-between gap-2", children: [heading(headingLevel, 'Authorization', ctx), _jsx(SelectTabTrigger, { items: names, className: "mb-4" })] }), securities.map((security, i) => (_jsx(SelectTab, { value: names[i], children: Object.entries(security).map(([key, scopes]) => { const scheme = securitySchemes?.[key]; if (!scheme) return; return (_jsx(AuthScheme, { scheme: scheme, scopes: scopes, ctx: ctx }, key)); }) }, i)))] })); } if (method.callbacks) { const callbacks = Object.entries(method.callbacks); callbacksNode = (_jsxs(SelectTabs, { defaultValue: callbacks[0][0], children: [_jsxs("div", { className: "flex justify-between gap-2 items-end", children: [heading(headingLevel, 'Callbacks', ctx), _jsx(SelectTabTrigger, { items: callbacks.map((v) => v[0]), className: "mb-4" })] }), callbacks.map(([name, callback]) => (_jsx(SelectTab, { value: name, children: _jsx(WebhookCallback, { callback: callback, ctx: ctx, headingLevel: headingLevel }) }, name)))] })); } const info = (_jsxs(ctx.renderer.APIInfo, { head: headNode, method: method.method, route: path, children: [type === 'operation' ? (ctx.disablePlayground ? (_jsxs("div", { className: "flex flex-row items-center gap-2.5 p-3 rounded-xl border bg-fd-card text-fd-card-foreground not-prose", children: [_jsx(MethodLabel, { className: "text-xs", children: method.method }), _jsx("code", { className: "flex-1 overflow-auto text-nowrap text-[13px] text-fd-muted-foreground", children: path })] })) : (_jsx(ctx.renderer.APIPlayground, { path: path, method: method, ctx: ctx }))) : null, authNode, parameterNode, bodyNode, responseNode, callbacksNode] })); if (type === 'operation') { const examples = getAPIExamples(path, method, ctx); return (_jsx(ctx.renderer.API, { children: _jsxs(APIExampleProvider, { route: path, examples: examples, method: method, children: [info, _jsx(APIExample, { examples: examples, method: method, ctx: ctx })] }) })); } else { return info; } } async function ResponseAccordion({ status, operation, ctx, }) { const response = operation.responses[status]; const { generateTypeScriptSchema, schema: { dereferenceMap }, } = ctx; const contentTypes = response.content ? Object.entries(response.content) : null; return (_jsxs(SelectTabs, { defaultValue: contentTypes?.[0][0], children: [_jsxs(AccordionHeader, { children: [_jsx(AccordionTrigger, { className: "font-mono", children: status }), contentTypes && (_jsx(SelectTabTrigger, { items: contentTypes.map((v) => v[0]) }))] }), _jsxs(AccordionContent, { className: "ps-4.5", children: [response.description && (_jsx("div", { className: "prose-no-margin", children: _jsx(Markdown, { text: response.description }) })), contentTypes?.map(async ([type, resType]) => { const schema = resType.schema; let ts; if (generateTypeScriptSchema) { ts = await generateTypeScriptSchema(operation, status); } else if (generateTypeScriptSchema === undefined && schema) { ts = await getTypescriptSchema(schema, dereferenceMap); } return (_jsxs(SelectTab, { value: type, className: "my-2", children: [ts && _jsx(CopyResponseTypeScript, { code: ts }), schema && (_jsx("div", { className: "border px-3 py-2 rounded-lg overflow-auto max-h-[400px]", children: _jsx(Schema, { name: "response", schema: schema, as: "body", readOnly: true, ctx: ctx }) }))] }, type)); })] })] })); } function WebhookCallback({ callback, ctx, headingLevel, }) { const pathItems = Object.entries(callback); return (_jsx(Accordions, { type: "single", collapsible: true, children: pathItems.map(([path, pathItem]) => { const pathNodes = methodKeys.map((method) => { const operation = pathItem[method]; if (!operation) return null; return (_jsx("div", { className: "border p-3 my-2 prose-no-margin rounded-lg", children: _jsx(Operation, { type: "webhook", path: path, headingLevel: headingLevel + 1, method: createMethod(method, pathItem, operation), ctx: ctx }) }, method)); }); return (_jsxs(AccordionItem, { value: path, children: [_jsx(AccordionHeader, { children: _jsx(AccordionTrigger, { className: "font-mono", children: path }) }), _jsx(AccordionContent, { children: pathNodes })] }, path)); }) })); } function AuthScheme({ scheme: schema, scopes, ctx: { renderer }, }) { const scopeElement = scopes.length > 0 ? (_jsxs("p", { children: ["Scope: ", _jsx("code", { children: scopes.join(', ') })] })) : null; if (schema.type === 'http' || schema.type === 'oauth2') { return (_jsxs(renderer.Property, { name: "Authorization", type: schema.type === 'http' && schema.scheme === 'basic' ? `Basic <token>` : 'Bearer <token>', required: true, children: [schema.description && _jsx(Markdown, { text: schema.description }), _jsxs("p", { children: ["In: ", _jsx("code", { children: "header" })] }), scopeElement] })); } if (schema.type === 'apiKey') { return (_jsxs(renderer.Property, { name: schema.name, type: "<token>", children: [schema.description && _jsx(Markdown, { text: schema.description }), _jsxs("p", { children: ["In: ", _jsx("code", { children: schema.in }), scopeElement] })] })); } if (schema.type === 'openIdConnect') { return (_jsxs(renderer.Property, { name: "OpenID Connect", type: "<token>", required: true, children: [schema.description && _jsx(Markdown, { text: schema.description }), scopeElement] })); } }