Skip to content

Commit 40c5130

Browse files
committed
Fix generate schema
1 parent c6c364e commit 40c5130

File tree

2 files changed

+122
-27
lines changed

2 files changed

+122
-27
lines changed

packages/react-openapi/src/generateSchemaExample.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,4 +1037,77 @@ describe('generateSchemaExample', () => {
10371037
})
10381038
).toBeUndefined();
10391039
});
1040+
1041+
it('handles oneOf -> allOf returning a string without spreading characters', () => {
1042+
// Create a circular reference that will return "[Circular Reference]" string
1043+
const circularSchema = {
1044+
type: 'object',
1045+
properties: {
1046+
nested: {},
1047+
},
1048+
} satisfies OpenAPIV3.SchemaObject;
1049+
circularSchema.properties.nested = circularSchema;
1050+
1051+
const schema = {
1052+
type: 'object',
1053+
properties: {
1054+
foo: {
1055+
type: 'string',
1056+
},
1057+
},
1058+
oneOf: [
1059+
{
1060+
allOf: [circularSchema],
1061+
},
1062+
],
1063+
} satisfies OpenAPIV3.SchemaObject;
1064+
1065+
const result = generateSchemaExample(schema);
1066+
1067+
expect(result).toBeDefined();
1068+
expect(result).toHaveProperty('foo');
1069+
});
1070+
1071+
it('merges object properties from oneOf -> allOf', () => {
1072+
const schema = {
1073+
type: 'object',
1074+
properties: {
1075+
discriminator: {
1076+
type: 'string',
1077+
},
1078+
},
1079+
oneOf: [
1080+
{
1081+
allOf: [
1082+
{
1083+
type: 'object',
1084+
properties: {
1085+
bar: {
1086+
type: 'string',
1087+
},
1088+
},
1089+
},
1090+
{
1091+
type: 'object',
1092+
properties: {
1093+
baz: {
1094+
type: 'number',
1095+
},
1096+
},
1097+
},
1098+
{
1099+
type: 'string', // This will return a string, but should be ignored
1100+
},
1101+
],
1102+
},
1103+
],
1104+
} satisfies OpenAPIV3.SchemaObject;
1105+
1106+
const result = generateSchemaExample(schema);
1107+
1108+
expect(result).toBeDefined();
1109+
expect(result).toHaveProperty('discriminator');
1110+
expect(result).toHaveProperty('bar');
1111+
expect(result).toHaveProperty('baz');
1112+
});
10401113
});

packages/react-openapi/src/generateSchemaExample.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ function guessFromFormat(schema: Record<string, any>, fallback = '') {
9999
return genericExampleValues[schema.format] ?? fallback;
100100
}
101101

102+
/**
103+
* Check if a value is a plain object (not null, not array)
104+
*/
105+
function isPlainObject(value: unknown): value is Record<string, unknown> {
106+
return typeof value === 'object' && value !== null && !Array.isArray(value);
107+
}
108+
102109
/**
103110
* This function takes an OpenAPI schema and generates an example from it
104111
* Forked from : https://github.com/scalar/scalar/blob/main/packages/oas-utils/src/spec-getters/getExampleFromSchema.ts
@@ -147,6 +154,23 @@ const getExampleFromSchema = (
147154
return result;
148155
}
149156

157+
// Process allOf items and merge object results into the response
158+
function mergeAllOfIntoResponse(
159+
allOfItems: Record<string, unknown>[],
160+
response: Record<string, unknown>,
161+
parent: Record<string, unknown> | undefined
162+
): void {
163+
const allOfResults = allOfItems
164+
.map((item: Record<string, unknown>) =>
165+
getExampleFromSchema(item, options, level + 1, parent, undefined, resultCache)
166+
)
167+
.filter(isPlainObject);
168+
169+
if (allOfResults.length > 0) {
170+
Object.assign(response, ...allOfResults);
171+
}
172+
}
173+
150174
// Check if the result is already cached
151175
if (resultCache.has(schema)) {
152176
return resultCache.get(schema);
@@ -307,45 +331,43 @@ const getExampleFromSchema = (
307331
}
308332

309333
if (schema.anyOf !== undefined) {
310-
Object.assign(
311-
response,
312-
getExampleFromSchema(
313-
schema.anyOf[0],
334+
const anyOfItem = schema.anyOf[0];
335+
// If anyOf[0] has allOf, process allOf items individually to merge object results
336+
if (anyOfItem?.allOf !== undefined && Array.isArray(anyOfItem.allOf)) {
337+
mergeAllOfIntoResponse(anyOfItem.allOf, response, anyOfItem);
338+
} else {
339+
const anyOfResult = getExampleFromSchema(
340+
anyOfItem,
314341
options,
315342
level + 1,
316343
undefined,
317344
undefined,
318345
resultCache
319-
)
320-
);
346+
);
347+
if (isPlainObject(anyOfResult)) {
348+
Object.assign(response, anyOfResult);
349+
}
350+
}
321351
} else if (schema.oneOf !== undefined) {
322-
Object.assign(
323-
response,
324-
getExampleFromSchema(
325-
schema.oneOf[0],
352+
const oneOfItem = schema.oneOf[0];
353+
// If oneOf[0] has allOf, process allOf items individually to merge object results
354+
if (oneOfItem?.allOf !== undefined && Array.isArray(oneOfItem.allOf)) {
355+
mergeAllOfIntoResponse(oneOfItem.allOf, response, oneOfItem);
356+
} else {
357+
const oneOfResult = getExampleFromSchema(
358+
oneOfItem,
326359
options,
327360
level + 1,
328361
undefined,
329362
undefined,
330363
resultCache
331-
)
332-
);
364+
);
365+
if (isPlainObject(oneOfResult)) {
366+
Object.assign(response, oneOfResult);
367+
}
368+
}
333369
} else if (schema.allOf !== undefined) {
334-
Object.assign(
335-
response,
336-
...schema.allOf
337-
.map((item: Record<string, any>) =>
338-
getExampleFromSchema(
339-
item,
340-
options,
341-
level + 1,
342-
schema,
343-
undefined,
344-
resultCache
345-
)
346-
)
347-
.filter((item: any) => item !== undefined)
348-
);
370+
mergeAllOfIntoResponse(schema.allOf, response, schema);
349371
}
350372

351373
return cache(schema, response);

0 commit comments

Comments
 (0)