Skip to content

Commit edddf8c

Browse files
committed
wip fix root refs
1 parent d0b1dfa commit edddf8c

File tree

2 files changed

+238
-1
lines changed

2 files changed

+238
-1
lines changed

packages/core/src/util/resolvers.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,30 @@ export const resolveSchema = (
114114
schemaPath: string,
115115
rootSchema: JsonSchema
116116
): JsonSchema => {
117+
// Special case: If the schema is the root schema and has a $ref at the top level
118+
// Directly resolve the reference in the root schema first
119+
if (schema === rootSchema && typeof schema?.$ref === 'string') {
120+
const refSegments = schema.$ref.split('/').map(decode);
121+
122+
// Create a temporary copy of rootSchema without $ref to prevent recursion
123+
// This is crucial for handling self-references in the root schema
124+
const tempRootSchema = { ...rootSchema };
125+
delete tempRootSchema.$ref;
126+
127+
// Resolve the reference within the temporary schema without the $ref
128+
// This allows us to resolve references like #/definitions/abc properly
129+
const resolvedRootSchema = resolveSchemaWithSegments(
130+
tempRootSchema,
131+
refSegments,
132+
tempRootSchema
133+
);
134+
135+
// If successfully resolved, use that as our starting schema
136+
if (resolvedRootSchema) {
137+
schema = resolvedRootSchema;
138+
}
139+
}
140+
117141
const segments = schemaPath?.split('/').map(decode);
118142
return resolveSchemaWithSegments(schema, segments, rootSchema);
119143
};

packages/core/test/util/resolvers.test.ts

Lines changed: 214 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
THE SOFTWARE.
2424
*/
25-
import { resolveData, resolveSchema } from '../../src/util/resolvers';
2625
import test from 'ava';
26+
import { resolveData, resolveSchema } from '../../src/util/resolvers';
2727

2828
test('resolveSchema - resolves schema with any ', (t) => {
2929
const schema = {
@@ -231,3 +231,216 @@ test('resolveData - resolves data with % characters', (t) => {
231231
};
232232
t.deepEqual(resolveData(data, 'foo%'), '123');
233233
});
234+
235+
test('resolveSchema - root schema with top-level $ref', (t) => {
236+
const schema = {
237+
$ref: '#/definitions/person',
238+
definitions: {
239+
person: {
240+
type: 'object',
241+
properties: {
242+
name: { type: 'string' },
243+
age: { type: 'number' },
244+
},
245+
},
246+
},
247+
};
248+
249+
const resolved = resolveSchema(schema, '', schema);
250+
t.is(resolved.type, 'object');
251+
t.truthy(resolved.properties);
252+
t.truthy(resolved.properties?.name);
253+
t.truthy(resolved.properties?.age);
254+
});
255+
256+
test('resolveSchema - self-references in root schema', (t) => {
257+
const schema = {
258+
$ref: '#',
259+
type: 'object',
260+
properties: {
261+
name: { type: 'string' },
262+
child: { $ref: '#' },
263+
},
264+
};
265+
266+
const resolved = resolveSchema(schema, '', schema);
267+
t.is(resolved.type, 'object');
268+
t.truthy(resolved.properties);
269+
t.truthy(resolved.properties?.name);
270+
t.truthy(resolved.properties?.child);
271+
});
272+
273+
test('resolveSchema - nested paths after resolving root schema $ref', (t) => {
274+
const schema = {
275+
$ref: '#/definitions/parent',
276+
definitions: {
277+
parent: {
278+
type: 'object',
279+
properties: {
280+
child: { $ref: '#/definitions/child' },
281+
},
282+
},
283+
child: {
284+
type: 'object',
285+
properties: {
286+
name: { type: 'string' },
287+
},
288+
},
289+
},
290+
};
291+
292+
// First resolve the root schema
293+
const resolved = resolveSchema(schema, '', schema);
294+
t.is(resolved.type, 'object');
295+
296+
// Then resolve the path to the child properties
297+
const childSchema = resolveSchema(schema, '/properties/child', schema);
298+
t.is(childSchema.type, 'object');
299+
t.truthy(childSchema.properties);
300+
t.is(childSchema.properties?.name.type, 'string');
301+
});
302+
303+
test('resolveSchema should resolve a root schema with top-level $ref', (t) => {
304+
const schema = {
305+
$ref: '#/definitions/person',
306+
definitions: {
307+
person: {
308+
type: 'object',
309+
properties: {
310+
name: { type: 'string' },
311+
age: { type: 'number' },
312+
},
313+
},
314+
},
315+
};
316+
317+
const resolved = resolveSchema(schema, '', schema);
318+
t.is(resolved.type, 'object');
319+
t.deepEqual(resolved.properties?.name, { type: 'string' });
320+
t.deepEqual(resolved.properties?.age, { type: 'number' });
321+
});
322+
323+
test('resolveSchema should handle self-references in root schema', (t) => {
324+
const schema = {
325+
$ref: '#',
326+
type: 'object',
327+
properties: {
328+
name: { type: 'string' },
329+
child: { $ref: '#' },
330+
},
331+
};
332+
333+
const resolved = resolveSchema(schema, '', schema);
334+
t.is(resolved.type, 'object');
335+
t.deepEqual(resolved.properties?.name, { type: 'string' });
336+
t.is(resolved.properties?.child.$ref, '#');
337+
});
338+
339+
test('resolveSchema should resolve nested paths after resolving root schema $ref', (t) => {
340+
const schema = {
341+
$ref: '#/definitions/parent',
342+
definitions: {
343+
parent: {
344+
type: 'object',
345+
properties: {
346+
child: { $ref: '#/definitions/child' },
347+
},
348+
},
349+
child: {
350+
type: 'object',
351+
properties: {
352+
name: { type: 'string' },
353+
},
354+
},
355+
},
356+
};
357+
358+
const childSchema = resolveSchema(schema, '/properties/child', schema);
359+
t.is(childSchema.type, 'object');
360+
t.deepEqual(childSchema.properties?.name, { type: 'string' });
361+
});
362+
363+
describe('resolveSchema - root schema with $ref', () => {
364+
test('should resolve root schema with top-level $ref', () => {
365+
const rootSchema = {
366+
$ref: '#/definitions/person',
367+
definitions: {
368+
person: {
369+
type: 'object',
370+
properties: {
371+
name: { type: 'string' },
372+
age: { type: 'number' },
373+
},
374+
},
375+
},
376+
};
377+
378+
const resolved = resolveSchema(rootSchema, '', rootSchema);
379+
expect(resolved).toBeDefined();
380+
expect(resolved.type).toBe('object');
381+
expect(resolved.properties).toBeDefined();
382+
if (resolved.properties) {
383+
expect(resolved.properties.name).toBeDefined();
384+
expect(resolved.properties.age).toBeDefined();
385+
}
386+
});
387+
388+
test('should handle self-references in root schema', () => {
389+
const rootSchema = {
390+
$ref: '#',
391+
type: 'object',
392+
properties: {
393+
name: { type: 'string' },
394+
child: { $ref: '#' },
395+
},
396+
};
397+
398+
const resolved = resolveSchema(rootSchema, '', rootSchema);
399+
expect(resolved).toBeDefined();
400+
expect(resolved.type).toBe('object');
401+
expect(resolved.properties).toBeDefined();
402+
if (resolved.properties) {
403+
expect(resolved.properties.name).toBeDefined();
404+
expect(resolved.properties.child).toBeDefined();
405+
}
406+
});
407+
408+
test('should correctly resolve nested paths after resolving root schema $ref', () => {
409+
const rootSchema = {
410+
$ref: '#/definitions/parent',
411+
definitions: {
412+
parent: {
413+
type: 'object',
414+
properties: {
415+
child: { $ref: '#/definitions/child' },
416+
},
417+
},
418+
child: {
419+
type: 'object',
420+
properties: {
421+
name: { type: 'string' },
422+
},
423+
},
424+
},
425+
};
426+
427+
// First resolve the root schema
428+
const resolved = resolveSchema(rootSchema, '', rootSchema);
429+
expect(resolved).toBeDefined();
430+
expect(resolved.type).toBe('object');
431+
432+
// Then resolve the path to the child properties
433+
const childSchema = resolveSchema(
434+
rootSchema,
435+
'/properties/child',
436+
rootSchema
437+
);
438+
expect(childSchema).toBeDefined();
439+
expect(childSchema.type).toBe('object');
440+
expect(childSchema.properties).toBeDefined();
441+
if (childSchema.properties) {
442+
expect(childSchema.properties.name).toBeDefined();
443+
expect(childSchema.properties.name.type).toBe('string');
444+
}
445+
});
446+
});

0 commit comments

Comments
 (0)