OpenAPI: Display complex types & self-referencing types

This commit is contained in:
fuma-nama
2024-07-08 16:12:03 +08:00
parent 0a827cab65
commit 81fde3f114
6 changed files with 72 additions and 42 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': minor
---
Support complex types & self-referencing types
+54 -29
View File
@@ -22,9 +22,11 @@ interface Context {
required: boolean;
/** Parse object */
/** Render the full object */
parseObject: boolean;
stack: OpenAPI.SchemaObject[];
render: RenderContext;
}
@@ -33,6 +35,17 @@ function isObject(schema: OpenAPI.SchemaObject): boolean {
}
export function schemaElement(
name: string,
schema: OpenAPI.SchemaObject,
ctx: Omit<Context, 'stack'>,
): string {
return render(name, schema, {
...ctx,
stack: [],
});
}
function render(
name: string,
schema: OpenAPI.SchemaObject,
ctx: Context,
@@ -63,7 +76,7 @@ export function schemaElement(
);
} else if (additionalProperties) {
child.push(
schemaElement('[key: string]', noRef(additionalProperties), {
render('[key: string]', noRef(additionalProperties), {
...ctx,
required: false,
parseObject: false,
@@ -73,7 +86,7 @@ export function schemaElement(
Object.entries(properties ?? {}).forEach(([key, value]) => {
child.push(
schemaElement(key, noRef(value), {
render(key, noRef(value), {
...ctx,
required: schema.required?.includes(key) ?? false,
parseObject: false,
@@ -99,38 +112,41 @@ export function schemaElement(
);
}
const mentionedObjectTypes = [
...(schema.anyOf ?? schema.oneOf ?? schema.allOf ?? []),
...(schema.type === 'array' ? [schema.items] : []),
]
.map(noRef)
.filter((s) => isObject(s));
child.push(
...mentionedObjectTypes.map((s, idx) =>
renderer.ObjectCollapsible(
{ name: s.title ?? `Object ${(idx + 1).toString()}` },
[
schemaElement('element', noRef(s), {
...ctx,
parseObject: true,
required: false,
}),
],
),
),
);
if (isObject(schema) && !ctx.parseObject) {
child.push(
renderer.ObjectCollapsible({ name }, [
schemaElement(name, schema, {
render(name, schema, {
...ctx,
parseObject: true,
required: false,
}),
]),
);
} else {
const mentionedObjectTypes = [
...(schema.anyOf ?? schema.oneOf ?? schema.allOf ?? []),
...(schema.not ? [schema.not] : []),
...(schema.type === 'array' ? [schema.items] : []),
]
.map(noRef)
.filter((s) => isComplexType(s) && !ctx.stack.includes(s));
ctx.stack.push(schema);
child.push(
...mentionedObjectTypes.map((s, idx) =>
renderer.ObjectCollapsible(
{ name: s.title ?? `Object ${(idx + 1).toString()}` },
[
render('element', noRef(s), {
...ctx,
parseObject: true,
required: false,
}),
],
),
),
);
ctx.stack.pop();
}
return renderer.Property(
@@ -144,9 +160,16 @@ export function schemaElement(
);
}
function getSchemaType(schema: OpenAPI.SchemaObject): string {
if (schema.title) return schema.title;
/**
* Check if the schema needs another collapsible to explain
*/
function isComplexType(schema: OpenAPI.SchemaObject): boolean {
if (schema.anyOf ?? schema.oneOf ?? schema.allOf) return true;
return isObject(schema) || schema.type === 'array';
}
function getSchemaType(schema: OpenAPI.SchemaObject): string {
if (schema.nullable) {
const type = getSchemaType({ ...schema, nullable: false });
@@ -154,8 +177,10 @@ function getSchemaType(schema: OpenAPI.SchemaObject): string {
return type === 'unknown' ? 'null' : `${type} | null`;
}
if (schema.title) return schema.title;
if (schema.type === 'array')
return `array of ${getSchemaType(noRef(schema.items))}`;
return `array<${getSchemaType(noRef(schema.items))}>`;
if (schema.oneOf)
return schema.oneOf.map((one) => getSchemaType(noRef(one))).join(' | ');
+2 -2
View File
@@ -215,7 +215,7 @@ Description of the special event
</Property>
<Property name={"dates"} type={"array of string"} required={true} deprecated={undefined}>
<Property name={"dates"} type={"array<string>"} required={true} deprecated={undefined}>
List of planned dates for the special event
@@ -516,7 +516,7 @@ Description of the special event
</Property>
<Property name={"dates"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"dates"} type={"array<string>"} required={false} deprecated={undefined}>
List of planned dates for the special event
+4 -4
View File
@@ -1138,7 +1138,7 @@ This is a place for dynamic meta data, anything that feels useful for you should
</Property>
<Property name={"roles"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"roles"} type={"array<string>"} required={false} deprecated={undefined}>
A list of roles that this key should have. If the role does not exist, an error is thrown
@@ -1146,7 +1146,7 @@ A list of roles that this key should have. If the role does not exist, an error
</Property>
<Property name={"permissions"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"permissions"} type={"array<string>"} required={false} deprecated={undefined}>
A list of permissions that this key should have. If the permission does not exist, an error is thrown
@@ -1790,7 +1790,7 @@ A query for which permissions you require
<ObjectCollapsible name={"And"}>
<Property name={"and"} type={"array of LiteralClause | And | Or | null"} required={true} deprecated={undefined}>
<Property name={"and"} type={"array<LiteralClause | And | Or | null>"} required={true} deprecated={undefined}>
</Property>
@@ -1798,7 +1798,7 @@ A query for which permissions you require
<ObjectCollapsible name={"Or"}>
<Property name={"or"} type={"array of LiteralClause | And | Or | null"} required={true} deprecated={undefined}>
<Property name={"or"} type={"array<LiteralClause | And | Or | null>"} required={true} deprecated={undefined}>
</Property>
@@ -23,7 +23,7 @@ In: `header`
### Request Body
<Property name={"body"} type={"array of object"} required={true} deprecated={undefined}>
<Property name={"body"} type={"array<object>"} required={true} deprecated={undefined}>
<ObjectCollapsible name={"Object 1"}>
@@ -119,7 +119,7 @@ This is a place for dynamic meta data, anything that feels useful for you should
</Property>
<Property name={"roles"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"roles"} type={"array<string>"} required={false} deprecated={undefined}>
A list of roles that this key should have. If the role does not exist, an error is thrown
@@ -127,7 +127,7 @@ A list of roles that this key should have. If the role does not exist, an error
</Property>
<Property name={"permissions"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"permissions"} type={"array<string>"} required={false} deprecated={undefined}>
A list of permissions that this key should have. If the permission does not exist, an error is thrown
@@ -762,7 +762,7 @@ The id of the api, you want to migrate keys to
</Property>
<Property name={"keys"} type={"array of object"} required={true} deprecated={undefined}>
<Property name={"keys"} type={"array<object>"} required={true} deprecated={undefined}>
<ObjectCollapsible name={"Object 1"}>
@@ -850,7 +850,7 @@ This is a place for dynamic meta data, anything that feels useful for you should
</Property>
<Property name={"roles"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"roles"} type={"array<string>"} required={false} deprecated={undefined}>
A list of roles that this key should have. If the role does not exist, an error is thrown
@@ -858,7 +858,7 @@ A list of roles that this key should have. If the role does not exist, an error
</Property>
<Property name={"permissions"} type={"array of string"} required={false} deprecated={undefined}>
<Property name={"permissions"} type={"array<string>"} required={false} deprecated={undefined}>
A list of permissions that this key should have. If the permission does not exist, an error is thrown
@@ -98,7 +98,7 @@ Attach any metadata to this request
</Property>
<Property name={"resources"} type={"array of object"} required={false} deprecated={undefined}>
<Property name={"resources"} type={"array<object>"} required={false} deprecated={undefined}>
Resources that are about to be accessed by the user