Skip to content

Commit 0b93a31

Browse files
committed
OpenAPI: Support required in allOf schemas
1 parent 54add15 commit 0b93a31

File tree

4 files changed

+258
-34
lines changed

4 files changed

+258
-34
lines changed

.changeset/breezy-paws-live.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'fumadocs-openapi': patch
3+
---
4+
5+
Support `required` in `allOf` schemas

packages/openapi/src/render/schema.tsx

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { OpenAPIV3 as OpenAPI } from 'openapi-types';
22
import { Fragment, type ReactNode } from 'react';
33
import { noRef } from '@/utils/schema';
44
import type { RenderContext } from '@/types';
5+
import { combineSchema } from '@/utils/combine-schema';
56
import { Markdown } from './markdown';
67

78
const keys: {
@@ -224,40 +225,6 @@ export function Schema({
224225
);
225226
}
226227

227-
/**
228-
* Combine multiple object schemas into one
229-
*/
230-
function combineSchema(schema: OpenAPI.SchemaObject[]): OpenAPI.SchemaObject {
231-
const result: OpenAPI.SchemaObject = {
232-
type: 'object',
233-
};
234-
235-
function add(s: OpenAPI.SchemaObject): void {
236-
if (s.properties) {
237-
result.properties ??= {};
238-
Object.assign(result.properties, s.properties);
239-
}
240-
241-
if (s.additionalProperties === true) {
242-
result.additionalProperties = true;
243-
} else if (
244-
s.additionalProperties &&
245-
typeof result.additionalProperties !== 'boolean'
246-
) {
247-
result.additionalProperties ??= {};
248-
Object.assign(result.additionalProperties, s.additionalProperties);
249-
}
250-
251-
if (s.allOf) {
252-
noRef(s.allOf).forEach(add);
253-
}
254-
}
255-
256-
schema.forEach(add);
257-
258-
return result;
259-
}
260-
261228
/**
262229
* Check if the schema needs another collapsible to explain
263230
*/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { type OpenAPIV3 as OpenAPI } from 'openapi-types';
2+
import { noRef } from '@/utils/schema';
3+
4+
/**
5+
* Combine multiple object schemas into one
6+
*/
7+
export function combineSchema(
8+
schema: OpenAPI.SchemaObject[],
9+
): OpenAPI.SchemaObject {
10+
const result: OpenAPI.SchemaObject = {
11+
type: 'object',
12+
};
13+
14+
function add(s: OpenAPI.SchemaObject): void {
15+
if (s.properties) {
16+
result.properties ??= {};
17+
Object.assign(result.properties, s.properties);
18+
}
19+
20+
if (s.additionalProperties === true) {
21+
result.additionalProperties = true;
22+
} else if (
23+
s.additionalProperties &&
24+
typeof result.additionalProperties !== 'boolean'
25+
) {
26+
result.additionalProperties ??= {};
27+
Object.assign(result.additionalProperties, s.additionalProperties);
28+
}
29+
30+
if (s.required) {
31+
result.required ??= [];
32+
result.required.push(...s.required);
33+
}
34+
35+
if (s.allOf) {
36+
noRef(s.allOf).forEach(add);
37+
}
38+
}
39+
40+
schema.forEach(add);
41+
42+
return result;
43+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { describe, expect, test } from 'vitest';
2+
import { combineSchema } from '@/utils/combine-schema';
3+
4+
describe('Merge object schemas', () => {
5+
test('Merge single object', () => {
6+
const result = combineSchema([
7+
{
8+
type: 'object',
9+
properties: {
10+
test: {
11+
type: 'string',
12+
enum: ['one', 'two'],
13+
},
14+
},
15+
},
16+
]);
17+
18+
expect(result).toMatchInlineSnapshot(`
19+
{
20+
"properties": {
21+
"test": {
22+
"enum": [
23+
"one",
24+
"two",
25+
],
26+
"type": "string",
27+
},
28+
},
29+
"type": "object",
30+
}
31+
`);
32+
});
33+
34+
test('Merge multiple objects', () => {
35+
const result = combineSchema([
36+
{
37+
type: 'object',
38+
properties: {
39+
test: {
40+
type: 'string',
41+
enum: ['one', 'two'],
42+
},
43+
},
44+
},
45+
{
46+
type: 'object',
47+
properties: {
48+
hello: {
49+
type: 'number',
50+
},
51+
},
52+
},
53+
]);
54+
55+
expect(result).toMatchInlineSnapshot(`
56+
{
57+
"properties": {
58+
"hello": {
59+
"type": "number",
60+
},
61+
"test": {
62+
"enum": [
63+
"one",
64+
"two",
65+
],
66+
"type": "string",
67+
},
68+
},
69+
"type": "object",
70+
}
71+
`);
72+
});
73+
74+
test('Merge multiple objects: required', () => {
75+
const result = combineSchema([
76+
{
77+
type: 'object',
78+
properties: {
79+
test: {
80+
type: 'string',
81+
enum: ['one', 'two'],
82+
},
83+
},
84+
},
85+
{
86+
type: 'object',
87+
properties: {
88+
hello: {
89+
type: 'number',
90+
},
91+
},
92+
required: ['hello'],
93+
},
94+
]);
95+
96+
expect(result).toMatchInlineSnapshot(`
97+
{
98+
"properties": {
99+
"hello": {
100+
"type": "number",
101+
},
102+
"test": {
103+
"enum": [
104+
"one",
105+
"two",
106+
],
107+
"type": "string",
108+
},
109+
},
110+
"required": [
111+
"hello",
112+
],
113+
"type": "object",
114+
}
115+
`);
116+
});
117+
118+
test('Merge multiple objects: additional properties', () => {
119+
const result = combineSchema([
120+
{
121+
type: 'object',
122+
properties: {
123+
test: {
124+
type: 'string',
125+
enum: ['one', 'two'],
126+
},
127+
},
128+
additionalProperties: true,
129+
},
130+
{
131+
type: 'object',
132+
additionalProperties: {
133+
type: 'string',
134+
},
135+
},
136+
]);
137+
138+
expect(result).toMatchInlineSnapshot(`
139+
{
140+
"additionalProperties": true,
141+
"properties": {
142+
"test": {
143+
"enum": [
144+
"one",
145+
"two",
146+
],
147+
"type": "string",
148+
},
149+
},
150+
"type": "object",
151+
}
152+
`);
153+
});
154+
155+
test('Merge multiple objects: `allOf`', () => {
156+
const result = combineSchema([
157+
{
158+
type: 'object',
159+
properties: {
160+
test: {
161+
type: 'string',
162+
enum: ['one', 'two'],
163+
},
164+
},
165+
},
166+
{
167+
allOf: [
168+
{
169+
type: 'object',
170+
properties: {
171+
hello: {
172+
type: 'number',
173+
},
174+
},
175+
},
176+
{
177+
type: 'object',
178+
properties: {
179+
world: {
180+
type: 'number',
181+
},
182+
},
183+
},
184+
],
185+
},
186+
]);
187+
188+
expect(result).toMatchInlineSnapshot(`
189+
{
190+
"properties": {
191+
"hello": {
192+
"type": "number",
193+
},
194+
"test": {
195+
"enum": [
196+
"one",
197+
"two",
198+
],
199+
"type": "string",
200+
},
201+
"world": {
202+
"type": "number",
203+
},
204+
},
205+
"type": "object",
206+
}
207+
`);
208+
});
209+
});

0 commit comments

Comments
 (0)