Skip to content

Commit d89c5cc

Browse files
authored
895 refactor to use visitor pattern for json schema traversal (#925)
* add new json schema visitor * adjust code to use json schema visitor * simplify test setup * add test for json schema visitor * add test for feature detection * make variables and parameters more descriptive * apply formatting changes --------- Co-authored-by: Logende <Logende@users.noreply.github.com>
1 parent 558246b commit d89c5cc

9 files changed

Lines changed: 967 additions & 279 deletions
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import {describe, expect, it, vi} from 'vitest';
2+
import {detectSchemaFeatures} from '@/schema/detectSchemaFeatures';
3+
import type {TopLevelSchema} from '@/schema/jsonSchemaType';
4+
5+
vi.mock('@/dataformats/formatRegistry', () => ({
6+
useDataConverter: () => ({
7+
stringify: (data: any) => JSON.stringify(data),
8+
parse: (data: string) => JSON.parse(data),
9+
}),
10+
}));
11+
12+
describe('detectSchemaFeatures', () => {
13+
it('detects no features in a minimal schema', () => {
14+
const schema: TopLevelSchema = {type: 'object'};
15+
const features = detectSchemaFeatures(schema);
16+
17+
expect(features.composition).toBe(false);
18+
expect(features.conditionals).toBe(false);
19+
expect(features.defaultValues).toBe(false);
20+
expect(features.exampleValues).toBe(false);
21+
expect(features.enums).toBe(false);
22+
expect(features.constants).toBe(false);
23+
expect(features.multipleTypes).toBe(false);
24+
expect(features.references).toBe(false);
25+
expect(features.required).toBe(false);
26+
expect(features.negation).toBe(false);
27+
expect(features.booleanSchemas).toBe(false);
28+
expect(features.descriptions).toBe(false);
29+
expect(features.titles).toBe(false);
30+
expect(features.externalReferences).toBe(false);
31+
});
32+
33+
it('detects composition (allOf/anyOf/oneOf)', () => {
34+
expect(detectSchemaFeatures({allOf: [{type: 'string'}]}).composition).toBe(true);
35+
expect(detectSchemaFeatures({anyOf: [{type: 'string'}]}).composition).toBe(true);
36+
expect(detectSchemaFeatures({oneOf: [{type: 'string'}, {type: 'number'}]}).composition).toBe(
37+
true
38+
);
39+
});
40+
41+
it('detects conditionals (if/then/else)', () => {
42+
expect(detectSchemaFeatures({if: {type: 'string'}, then: {minLength: 1}}).conditionals).toBe(
43+
true
44+
);
45+
expect(detectSchemaFeatures({then: {minLength: 1}}).conditionals).toBe(true);
46+
expect(detectSchemaFeatures({else: true}).conditionals).toBe(true);
47+
});
48+
49+
it('detects default values including falsy ones', () => {
50+
expect(detectSchemaFeatures({default: 'hello'}).defaultValues).toBe(true);
51+
expect(detectSchemaFeatures({default: 0}).defaultValues).toBe(true);
52+
expect(detectSchemaFeatures({default: false}).defaultValues).toBe(true);
53+
expect(detectSchemaFeatures({default: null}).defaultValues).toBe(true);
54+
expect(detectSchemaFeatures({default: ''}).defaultValues).toBe(true);
55+
});
56+
57+
it('detects example values', () => {
58+
expect(detectSchemaFeatures({examples: ['foo']}).exampleValues).toBe(true);
59+
});
60+
61+
it('detects enums', () => {
62+
expect(detectSchemaFeatures({enum: ['a', 'b']}).enums).toBe(true);
63+
});
64+
65+
it('detects constants including falsy ones', () => {
66+
expect(detectSchemaFeatures({const: 'foo'}).constants).toBe(true);
67+
expect(detectSchemaFeatures({const: 0}).constants).toBe(true);
68+
expect(detectSchemaFeatures({const: false}).constants).toBe(true);
69+
expect(detectSchemaFeatures({const: null}).constants).toBe(true);
70+
});
71+
72+
it('detects multiple types', () => {
73+
expect(detectSchemaFeatures({type: ['string', 'null']}).multipleTypes).toBe(true);
74+
expect(detectSchemaFeatures({type: 'string'}).multipleTypes).toBe(false);
75+
});
76+
77+
it('detects $ref references', () => {
78+
const schema: TopLevelSchema = {
79+
type: 'object',
80+
properties: {
81+
foo: {$ref: '#/$defs/bar'},
82+
},
83+
$defs: {bar: {type: 'string'}},
84+
};
85+
expect(detectSchemaFeatures(schema).references).toBe(true);
86+
expect(detectSchemaFeatures(schema).externalReferences).toBe(false);
87+
});
88+
89+
it('detects external references', () => {
90+
const schema: TopLevelSchema = {$ref: 'https://example.com/schema.json'};
91+
expect(detectSchemaFeatures(schema).references).toBe(true);
92+
expect(detectSchemaFeatures(schema).externalReferences).toBe(true);
93+
});
94+
95+
it('detects required', () => {
96+
expect(detectSchemaFeatures({type: 'object', required: ['name']}).required).toBe(true);
97+
});
98+
99+
it('detects negation (not)', () => {
100+
expect(detectSchemaFeatures({not: {type: 'string'}}).negation).toBe(true);
101+
});
102+
103+
it('detects boolean schemas', () => {
104+
const schema: TopLevelSchema = {
105+
type: 'object',
106+
properties: {
107+
anything: true,
108+
nothing: false,
109+
},
110+
} as TopLevelSchema;
111+
expect(detectSchemaFeatures(schema).booleanSchemas).toBe(true);
112+
});
113+
114+
it('detects descriptions', () => {
115+
expect(detectSchemaFeatures({description: 'A schema'}).descriptions).toBe(true);
116+
});
117+
118+
it('detects titles', () => {
119+
expect(detectSchemaFeatures({title: 'My Schema'}).titles).toBe(true);
120+
});
121+
122+
it('detects features in nested schemas', () => {
123+
const schema: TopLevelSchema = {
124+
type: 'object',
125+
properties: {
126+
nested: {
127+
type: 'object',
128+
properties: {
129+
deep: {
130+
enum: ['a', 'b'],
131+
default: 'a',
132+
description: 'A deep field',
133+
},
134+
},
135+
},
136+
},
137+
};
138+
const features = detectSchemaFeatures(schema);
139+
expect(features.enums).toBe(true);
140+
expect(features.defaultValues).toBe(true);
141+
expect(features.descriptions).toBe(true);
142+
});
143+
144+
it('detects features inside $defs', () => {
145+
const schema: TopLevelSchema = {
146+
$defs: {
147+
myType: {
148+
type: 'string',
149+
title: 'My Type',
150+
allOf: [{minLength: 1}],
151+
},
152+
},
153+
};
154+
const features = detectSchemaFeatures(schema);
155+
expect(features.titles).toBe(true);
156+
expect(features.composition).toBe(true);
157+
});
158+
159+
it('does not fail on malformed schema in lenient mode (default)', () => {
160+
const schema = {type: 123} as any;
161+
expect(() => detectSchemaFeatures(schema)).not.toThrow();
162+
});
163+
});

0 commit comments

Comments
 (0)