mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
OpenAPI: Fix gaps of property components under parameters section
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Fix gaps of property components under parameters section
|
||||
@@ -2,7 +2,6 @@ import { createMDXSource } from 'fumadocs-mdx';
|
||||
import { loader } from 'fumadocs-core/source';
|
||||
import { attachFile, createOpenAPI } from 'fumadocs-openapi/server';
|
||||
import { docs, meta } from '@/.source';
|
||||
import { APIPlayground } from 'fumadocs-openapi/scalar';
|
||||
|
||||
export const source = loader({
|
||||
baseUrl: '/docs',
|
||||
@@ -12,8 +11,4 @@ export const source = loader({
|
||||
},
|
||||
});
|
||||
|
||||
export const openapi = createOpenAPI({
|
||||
renderer: {
|
||||
APIPlayground,
|
||||
},
|
||||
});
|
||||
export const openapi = createOpenAPI();
|
||||
|
||||
@@ -3,14 +3,25 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||
/**
|
||||
* Shared layout configurations
|
||||
*
|
||||
* you can configure layouts individually from:
|
||||
* you can customise layouts individually from:
|
||||
* Home Layout: app/(home)/layout.tsx
|
||||
* Docs Layout: app/docs/layout.tsx
|
||||
*/
|
||||
export const baseOptions: BaseLayoutProps = {
|
||||
nav: {
|
||||
// can be JSX too!
|
||||
title: 'My App',
|
||||
title: (
|
||||
<>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Logo"
|
||||
>
|
||||
<circle cx={12} cy={12} r={12} fill="currentColor" />
|
||||
</svg>
|
||||
My App
|
||||
</>
|
||||
),
|
||||
},
|
||||
links: [
|
||||
{
|
||||
|
||||
@@ -27,6 +27,13 @@ export interface CodeSample {
|
||||
| false;
|
||||
}
|
||||
|
||||
const ParamTypes = {
|
||||
path: 'Path Parameters',
|
||||
query: 'Query Parameters',
|
||||
header: 'Header Parameters',
|
||||
cookie: 'Cookie Parameters',
|
||||
};
|
||||
|
||||
export function Operation({
|
||||
type = 'operation',
|
||||
path,
|
||||
@@ -130,46 +137,42 @@ export function Operation({
|
||||
);
|
||||
}
|
||||
|
||||
const parameterGroups = new Map<string, ReactNode[]>();
|
||||
const endpoint = generateSample(path, method, ctx);
|
||||
|
||||
for (const param of method.parameters ?? []) {
|
||||
const pInfo = endpoint.parameters.find(
|
||||
(item) => item.name === param.name && item.in === param.in,
|
||||
const parameterNode = Object.entries(ParamTypes).map(([type, title]) => {
|
||||
const params = endpoint.parameters.filter(
|
||||
(param) => param.in === type && !param.isAuthOnly,
|
||||
);
|
||||
if (!pInfo) continue;
|
||||
if (params.length === 0) return;
|
||||
|
||||
const schema = pInfo.schema;
|
||||
const groupName =
|
||||
{
|
||||
path: 'Path Parameters',
|
||||
query: 'Query Parameters',
|
||||
header: 'Header Parameters',
|
||||
cookie: 'Cookie Parameters',
|
||||
}[param.in] ?? 'Other Parameters';
|
||||
|
||||
const group = parameterGroups.get(groupName) ?? [];
|
||||
group.push(
|
||||
<Schema
|
||||
key={param.name}
|
||||
name={param.name}
|
||||
schema={{
|
||||
...schema,
|
||||
description: param.description ?? schema.description,
|
||||
deprecated:
|
||||
(param.deprecated ?? false) || (schema.deprecated ?? false),
|
||||
}}
|
||||
ctx={{
|
||||
parseObject: false,
|
||||
readOnly: method.method === 'GET',
|
||||
writeOnly: method.method !== 'GET',
|
||||
required: param.required ?? false,
|
||||
render: ctx,
|
||||
}}
|
||||
/>,
|
||||
return (
|
||||
<Fragment key={type}>
|
||||
{heading(headingLevel, title, ctx)}
|
||||
<div className="flex flex-col gap-4">
|
||||
{params.map((param) => (
|
||||
<Schema
|
||||
key={param.name}
|
||||
name={param.name}
|
||||
schema={{
|
||||
...param.schema,
|
||||
description: param.description ?? param.schema?.description,
|
||||
deprecated:
|
||||
(param.deprecated ?? false) ||
|
||||
(param.schema?.deprecated ?? false),
|
||||
}}
|
||||
ctx={{
|
||||
parseObject: false,
|
||||
readOnly: method.method === 'GET',
|
||||
writeOnly: method.method !== 'GET',
|
||||
required: param.required ?? false,
|
||||
render: ctx,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
parameterGroups.set(groupName, group);
|
||||
}
|
||||
});
|
||||
|
||||
if (method.callbacks) {
|
||||
callbacksNode = (
|
||||
@@ -208,14 +211,7 @@ export function Operation({
|
||||
</>
|
||||
) : null}
|
||||
{bodyNode}
|
||||
{Array.from(parameterGroups.entries()).map(([group, params]) => {
|
||||
return (
|
||||
<Fragment key={group}>
|
||||
{heading(headingLevel, group, ctx)}
|
||||
{params}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{parameterNode}
|
||||
{responseNode}
|
||||
{callbacksNode}
|
||||
</ctx.renderer.APIInfo>
|
||||
@@ -277,7 +273,7 @@ function AuthSection({
|
||||
}: {
|
||||
requirements: SecurityRequirementObject[];
|
||||
ctx: RenderContext;
|
||||
}): ReactNode {
|
||||
}) {
|
||||
let id = 0;
|
||||
const info: ReactNode[] = [];
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ export function Schema({
|
||||
);
|
||||
}
|
||||
|
||||
let footer: ReactNode = null;
|
||||
let footer: ReactNode;
|
||||
const fields: {
|
||||
key: string;
|
||||
value: string;
|
||||
|
||||
@@ -45,7 +45,7 @@ export function getSampleRequest(
|
||||
inputToString(
|
||||
endpoint.body.samples[sampleKey]?.value ?? '',
|
||||
endpoint.body.mediaType,
|
||||
'backtick',
|
||||
'python',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export function Property({
|
||||
}: PropertyProps) {
|
||||
return (
|
||||
<div className="rounded-xl border bg-fd-card p-3 prose-no-margin">
|
||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
||||
<div className="flex flex-row flex-wrap items-center gap-4 mb-2">
|
||||
<code>{name}</code>
|
||||
{required ? (
|
||||
<Badge color="red" className="text-xs">
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
type NoReference,
|
||||
} from '@/utils/schema';
|
||||
import { getSecurities, getSecurityPrefix } from '@/utils/get-security';
|
||||
export interface EndpointSamples {
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
export interface Samples {
|
||||
[key: string]: {
|
||||
value?: unknown;
|
||||
description?: string;
|
||||
@@ -26,7 +28,7 @@ export interface EndpointSample {
|
||||
body?: {
|
||||
schema: ParsedSchema;
|
||||
mediaType: string;
|
||||
samples: EndpointSamples;
|
||||
samples: Samples;
|
||||
};
|
||||
responses: Record<string, ResponseSample>;
|
||||
parameters: ParameterSample[];
|
||||
@@ -38,11 +40,9 @@ interface ResponseSample {
|
||||
schema: ParsedSchema;
|
||||
}
|
||||
|
||||
interface ParameterSample {
|
||||
name: string;
|
||||
in: string;
|
||||
schema: ParsedSchema;
|
||||
interface ParameterSample extends NoReference<OpenAPIV3_1.ParameterObject> {
|
||||
sample: unknown;
|
||||
isAuthOnly: boolean;
|
||||
}
|
||||
|
||||
export function generateSample(
|
||||
@@ -54,35 +54,34 @@ export function generateSample(
|
||||
const responses: EndpointSample['responses'] = {};
|
||||
|
||||
for (const param of method.parameters ?? []) {
|
||||
if (param.schema) {
|
||||
let value = param.example ?? sample(param.schema as object);
|
||||
if (param.schema.type && param.schema.type === value) {
|
||||
// if no example is defined make sure its visible that there is still a placeholder, equal to auth <token>
|
||||
value = `<${value}>`;
|
||||
}
|
||||
params.push({
|
||||
name: param.name,
|
||||
in: param.in,
|
||||
schema: param.schema,
|
||||
sample: value,
|
||||
});
|
||||
} else if (param.content) {
|
||||
const key = getPreferredType(param.content);
|
||||
const content = key ? param.content[key] : undefined;
|
||||
let schema = param.schema,
|
||||
value;
|
||||
|
||||
if (!key || !content)
|
||||
if (!schema && param.content) {
|
||||
const key = getPreferredType(param.content);
|
||||
|
||||
const content = key ? param.content[key] : undefined;
|
||||
if (!content)
|
||||
throw new Error(
|
||||
`Cannot find parameter schema for ${param.name} in ${path} ${method.method}`,
|
||||
);
|
||||
|
||||
params.push({
|
||||
name: param.name,
|
||||
in: param.in,
|
||||
schema: content.schema ?? {},
|
||||
sample:
|
||||
content.example ?? param.example ?? sample(content.schema as object),
|
||||
});
|
||||
schema = content.schema;
|
||||
value = content.example ?? param.example ?? sample(schema as object);
|
||||
} else {
|
||||
value = param.example ?? sample(schema as object);
|
||||
}
|
||||
|
||||
if (schema?.type && param.schema?.type === value) {
|
||||
// if no example is defined make sure its visible that there is still a placeholder, equal to auth <token>
|
||||
value = `<${value}>`;
|
||||
}
|
||||
|
||||
params.push({
|
||||
...param,
|
||||
sample: value,
|
||||
isAuthOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
const requirements = method.security ?? document.security;
|
||||
@@ -97,6 +96,7 @@ export function generateSample(
|
||||
},
|
||||
sample: prefix ? `${prefix} <token>` : '<token>',
|
||||
in: 'header',
|
||||
isAuthOnly: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -147,17 +147,13 @@ export function generateSample(
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
for (const param of params) {
|
||||
let value = generateBody(method.method, param.schema);
|
||||
if (param.schema.type && param.schema.type === value) {
|
||||
// if no example is defined make sure its visible that there is still a placeholder, equal to auth <token>
|
||||
value = `<${value}>`;
|
||||
}
|
||||
if (param.in === 'query') queryParams.append(param.name, String(value));
|
||||
if (param.in === 'query')
|
||||
queryParams.append(param.name, String(param.sample));
|
||||
|
||||
if (param.in === 'path')
|
||||
pathWithParameters = pathWithParameters.replace(
|
||||
`{${param.name}}`,
|
||||
String(value),
|
||||
String(param.sample),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,15 @@ import { type ElementCompact, js2xml } from 'xml-js';
|
||||
export function inputToString(
|
||||
value: unknown,
|
||||
mediaType = 'application/json',
|
||||
multiLine: 'single-quote' | 'backtick' | 'none' = 'none',
|
||||
multiLine: 'single-quote' | 'backtick' | 'python' | 'none' = 'none',
|
||||
): string {
|
||||
const getStr = (v: string) => {
|
||||
if (multiLine === 'none') return JSON.stringify(v);
|
||||
|
||||
const delimit = multiLine === 'backtick' ? `\`` : `'`;
|
||||
const delimit = { backtick: `\``, 'single-quote': `'`, python: `"""` }[
|
||||
multiLine
|
||||
];
|
||||
|
||||
return `${delimit}${v.replaceAll(delimit, `\\${delimit}`)}${delimit}`;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user