OpenAPI: Support required in allOf schemas

This commit is contained in:
fuma-nama
2024-08-23 13:53:56 +08:00
parent 54add15696
commit 0b93a3180a
4 changed files with 258 additions and 34 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'fumadocs-openapi': patch
---
Support `required` in `allOf` schemas
+1 -34
View File
@@ -2,6 +2,7 @@ import type { OpenAPIV3 as OpenAPI } from 'openapi-types';
import { Fragment, type ReactNode } from 'react';
import { noRef } from '@/utils/schema';
import type { RenderContext } from '@/types';
import { combineSchema } from '@/utils/combine-schema';
import { Markdown } from './markdown';
const keys: {
@@ -224,40 +225,6 @@ export function Schema({
);
}
/**
* Combine multiple object schemas into one
*/
function combineSchema(schema: OpenAPI.SchemaObject[]): OpenAPI.SchemaObject {
const result: OpenAPI.SchemaObject = {
type: 'object',
};
function add(s: OpenAPI.SchemaObject): void {
if (s.properties) {
result.properties ??= {};
Object.assign(result.properties, s.properties);
}
if (s.additionalProperties === true) {
result.additionalProperties = true;
} else if (
s.additionalProperties &&
typeof result.additionalProperties !== 'boolean'
) {
result.additionalProperties ??= {};
Object.assign(result.additionalProperties, s.additionalProperties);
}
if (s.allOf) {
noRef(s.allOf).forEach(add);
}
}
schema.forEach(add);
return result;
}
/**
* Check if the schema needs another collapsible to explain
*/
@@ -0,0 +1,43 @@
import { type OpenAPIV3 as OpenAPI } from 'openapi-types';
import { noRef } from '@/utils/schema';
/**
* Combine multiple object schemas into one
*/
export function combineSchema(
schema: OpenAPI.SchemaObject[],
): OpenAPI.SchemaObject {
const result: OpenAPI.SchemaObject = {
type: 'object',
};
function add(s: OpenAPI.SchemaObject): void {
if (s.properties) {
result.properties ??= {};
Object.assign(result.properties, s.properties);
}
if (s.additionalProperties === true) {
result.additionalProperties = true;
} else if (
s.additionalProperties &&
typeof result.additionalProperties !== 'boolean'
) {
result.additionalProperties ??= {};
Object.assign(result.additionalProperties, s.additionalProperties);
}
if (s.required) {
result.required ??= [];
result.required.push(...s.required);
}
if (s.allOf) {
noRef(s.allOf).forEach(add);
}
}
schema.forEach(add);
return result;
}
+209
View File
@@ -0,0 +1,209 @@
import { describe, expect, test } from 'vitest';
import { combineSchema } from '@/utils/combine-schema';
describe('Merge object schemas', () => {
test('Merge single object', () => {
const result = combineSchema([
{
type: 'object',
properties: {
test: {
type: 'string',
enum: ['one', 'two'],
},
},
},
]);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"test": {
"enum": [
"one",
"two",
],
"type": "string",
},
},
"type": "object",
}
`);
});
test('Merge multiple objects', () => {
const result = combineSchema([
{
type: 'object',
properties: {
test: {
type: 'string',
enum: ['one', 'two'],
},
},
},
{
type: 'object',
properties: {
hello: {
type: 'number',
},
},
},
]);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"hello": {
"type": "number",
},
"test": {
"enum": [
"one",
"two",
],
"type": "string",
},
},
"type": "object",
}
`);
});
test('Merge multiple objects: required', () => {
const result = combineSchema([
{
type: 'object',
properties: {
test: {
type: 'string',
enum: ['one', 'two'],
},
},
},
{
type: 'object',
properties: {
hello: {
type: 'number',
},
},
required: ['hello'],
},
]);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"hello": {
"type": "number",
},
"test": {
"enum": [
"one",
"two",
],
"type": "string",
},
},
"required": [
"hello",
],
"type": "object",
}
`);
});
test('Merge multiple objects: additional properties', () => {
const result = combineSchema([
{
type: 'object',
properties: {
test: {
type: 'string',
enum: ['one', 'two'],
},
},
additionalProperties: true,
},
{
type: 'object',
additionalProperties: {
type: 'string',
},
},
]);
expect(result).toMatchInlineSnapshot(`
{
"additionalProperties": true,
"properties": {
"test": {
"enum": [
"one",
"two",
],
"type": "string",
},
},
"type": "object",
}
`);
});
test('Merge multiple objects: `allOf`', () => {
const result = combineSchema([
{
type: 'object',
properties: {
test: {
type: 'string',
enum: ['one', 'two'],
},
},
},
{
allOf: [
{
type: 'object',
properties: {
hello: {
type: 'number',
},
},
},
{
type: 'object',
properties: {
world: {
type: 'number',
},
},
},
],
},
]);
expect(result).toMatchInlineSnapshot(`
{
"properties": {
"hello": {
"type": "number",
},
"test": {
"enum": [
"one",
"two",
],
"type": "string",
},
"world": {
"type": "number",
},
},
"type": "object",
}
`);
});
});