docusaurus-plugin-generate-schema-docs 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/add-to-cart-event.json +45 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/choice-event.json +78 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/complex-event.json +193 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/components/dataLayer.json +56 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/components/product.json +126 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/purchase-event.json +73 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/root-any-of-event.json +40 -0
- package/__tests__/__fixtures__/validateSchemas/complex-validation/events/root-choice-event.json +54 -0
- package/__tests__/helpers/validator.test.js +115 -0
- package/__tests__/validateSchemas-integration.test.js +29 -0
- package/__tests__/validateSchemas.test.js +18 -102
- package/helpers/buildExampleFromSchema.js +44 -49
- package/helpers/file-system.js +1 -2
- package/helpers/schema-processing.js +0 -15
- package/helpers/schemaToExamples.js +46 -74
- package/helpers/validator.js +108 -0
- package/package.json +1 -2
- package/validateSchemas.js +70 -55
- package/__tests__/helpers/loadSchema.test.js +0 -20
- package/helpers/loadSchema.js +0 -11
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createValidator } from '../../helpers/validator';
|
|
2
|
+
|
|
3
|
+
describe('createValidator', () => {
|
|
4
|
+
it('creates a validator that returns true for valid data with no schema version (draft-07)', async () => {
|
|
5
|
+
const schema = {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
name: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
required: ['name'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const validator = await createValidator(schema);
|
|
16
|
+
const result = validator({ name: 'test' });
|
|
17
|
+
|
|
18
|
+
expect(result.valid).toBe(true);
|
|
19
|
+
expect(result.errors).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('creates a validator that returns false for invalid data with no schema version (draft-07)', async () => {
|
|
23
|
+
const schema = {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
name: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['name'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const validator = await createValidator(schema);
|
|
34
|
+
const result = validator({ name: 123 });
|
|
35
|
+
|
|
36
|
+
expect(result.valid).toBe(false);
|
|
37
|
+
expect(result.errors).not.toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('creates a validator that can validate against a draft-04 schema', async () => {
|
|
41
|
+
const schema = {
|
|
42
|
+
$schema: 'http://json-schema.org/draft-04/schema#',
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
name: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ['name'],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const validator = await createValidator(schema);
|
|
53
|
+
const result = validator({ name: 'test' });
|
|
54
|
+
|
|
55
|
+
expect(result.valid).toBe(true);
|
|
56
|
+
expect(result.errors).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('creates a validator that can validate against a draft-07 schema', async () => {
|
|
60
|
+
const schema = {
|
|
61
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
62
|
+
type: 'object',
|
|
63
|
+
properties: {
|
|
64
|
+
name: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['name'],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const validator = await createValidator(schema);
|
|
72
|
+
const result = validator({ name: 'test' });
|
|
73
|
+
|
|
74
|
+
expect(result.valid).toBe(true);
|
|
75
|
+
expect(result.errors).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('creates a validator that can validate against a 2019-09 schema', async () => {
|
|
79
|
+
const schema = {
|
|
80
|
+
$schema: 'https://json-schema.org/draft/2019-09/schema',
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
name: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['name'],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const validator = await createValidator(schema);
|
|
91
|
+
const result = validator({ name: 'test' });
|
|
92
|
+
|
|
93
|
+
expect(result.valid).toBe(true);
|
|
94
|
+
expect(result.errors).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('creates a validator that can validate against a 2020-12 schema', async () => {
|
|
98
|
+
const schema = {
|
|
99
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
name: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ['name'],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const validator = await createValidator(schema);
|
|
110
|
+
const result = validator({ name: 'test' });
|
|
111
|
+
|
|
112
|
+
expect(result.valid).toBe(true);
|
|
113
|
+
expect(result.errors).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import validateSchemas from '../validateSchemas';
|
|
7
|
+
import { getPathsForVersion } from '../helpers/path-helpers';
|
|
8
|
+
|
|
9
|
+
describe('validateSchemas - Integration', () => {
|
|
10
|
+
let consoleErrorSpy;
|
|
11
|
+
let consoleLogSpy;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Spy on console.error and console.log to keep test output clean
|
|
15
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
16
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
jest.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return true for the "next" version schemas', async () => {
|
|
24
|
+
const siteDir = path.resolve(__dirname, '../../../demo');
|
|
25
|
+
const { schemaDir } = getPathsForVersion('next', siteDir);
|
|
26
|
+
const result = await validateSchemas(schemaDir);
|
|
27
|
+
expect(result).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -14,124 +14,40 @@ describe('validateSchemas', () => {
|
|
|
14
14
|
|
|
15
15
|
beforeEach(() => {
|
|
16
16
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-test-'));
|
|
17
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
18
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
19
17
|
});
|
|
20
18
|
|
|
21
19
|
afterEach(() => {
|
|
22
20
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
23
|
-
jest.restoreAllMocks();
|
|
24
21
|
});
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
-
fs.writeFileSync(filePath, JSON.stringify(content, null, 2));
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const loadFixture = (fixtureName) => {
|
|
33
|
-
const fixturePath = path.resolve(
|
|
23
|
+
it('should return true for a complex set of valid schemas', async () => {
|
|
24
|
+
const fixtureDir = path.resolve(
|
|
34
25
|
__dirname,
|
|
35
26
|
'__fixtures__',
|
|
36
27
|
'validateSchemas',
|
|
37
|
-
|
|
28
|
+
'complex-validation',
|
|
38
29
|
);
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
it('should return true when all schemas are valid', async () => {
|
|
44
|
-
const validSchema = loadFixture('valid-schema.json');
|
|
45
|
-
writeSchema(tmpDir, 'valid-schema.json', validSchema);
|
|
46
|
-
|
|
47
|
-
const result = await validateSchemas(tmpDir);
|
|
30
|
+
const schemaDir = path.join(tmpDir, 'schemas');
|
|
31
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
32
|
+
fs.cpSync(fixtureDir, schemaDir, { recursive: true });
|
|
33
|
+
const result = await validateSchemas(schemaDir);
|
|
48
34
|
expect(result).toBe(true);
|
|
49
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
50
|
-
'OK Schema valid-schema.json produced a valid example.',
|
|
51
|
-
);
|
|
52
35
|
});
|
|
53
36
|
|
|
54
37
|
it('should return false if an example fails validation', async () => {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
61
|
-
'x Schema invalid-example-schema.json example data failed validation:',
|
|
38
|
+
const fixturePath = path.resolve(
|
|
39
|
+
__dirname,
|
|
40
|
+
'__fixtures__',
|
|
41
|
+
'validateSchemas',
|
|
42
|
+
'invalid-example-schema.json',
|
|
62
43
|
);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
message: 'must be number',
|
|
69
|
-
}),
|
|
70
|
-
]),
|
|
44
|
+
const schemaDir = path.join(tmpDir, 'schemas');
|
|
45
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
46
|
+
fs.copyFileSync(
|
|
47
|
+
fixturePath,
|
|
48
|
+
path.join(schemaDir, 'invalid-example-schema.json'),
|
|
71
49
|
);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
it('should fail validation for missing required property', async () => {
|
|
75
|
-
const noValidExampleSchema = loadFixture('no-example-schema.json');
|
|
76
|
-
writeSchema(tmpDir, 'no-valid-example-schema.json', noValidExampleSchema);
|
|
77
|
-
|
|
78
|
-
const result = await validateSchemas(tmpDir);
|
|
50
|
+
const result = await validateSchemas(schemaDir);
|
|
79
51
|
expect(result).toBe(false);
|
|
80
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
81
|
-
'x Schema no-valid-example-schema.json does not produce a valid example.',
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should handle schemas with $refs correctly', async () => {
|
|
86
|
-
const componentSchema = loadFixture(
|
|
87
|
-
path.join('components', 'referenced.json'),
|
|
88
|
-
);
|
|
89
|
-
const mainSchema = loadFixture('main-schema-with-ref.json');
|
|
90
|
-
|
|
91
|
-
writeSchema(
|
|
92
|
-
path.join(tmpDir, 'components'),
|
|
93
|
-
'referenced.json',
|
|
94
|
-
componentSchema,
|
|
95
|
-
);
|
|
96
|
-
writeSchema(tmpDir, 'main-schema-with-ref.json', mainSchema);
|
|
97
|
-
|
|
98
|
-
const result = await validateSchemas(tmpDir);
|
|
99
|
-
expect(result).toBe(true);
|
|
100
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
101
|
-
'OK Schema main-schema-with-ref.json produced a valid example.',
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should reject if a referenced schema is missing', async () => {
|
|
106
|
-
const mainSchema = loadFixture('main-schema-with-missing-ref.json');
|
|
107
|
-
writeSchema(tmpDir, 'main-schema-with-missing-ref.json', mainSchema);
|
|
108
|
-
|
|
109
|
-
const expectedErrorPath = path.join(tmpDir, 'non-existent-component.json');
|
|
110
|
-
await expect(validateSchemas(tmpDir)).rejects.toThrow(
|
|
111
|
-
expect.objectContaining({
|
|
112
|
-
message: expect.stringContaining(
|
|
113
|
-
`Error opening file ${expectedErrorPath}`,
|
|
114
|
-
),
|
|
115
|
-
}),
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should throw an error if duplicate schema IDs are found', async () => {
|
|
120
|
-
const schemaA = {
|
|
121
|
-
$id: 'duplicate-id',
|
|
122
|
-
type: 'object',
|
|
123
|
-
properties: { a: { type: 'string' } },
|
|
124
|
-
};
|
|
125
|
-
const schemaB = {
|
|
126
|
-
$id: 'duplicate-id',
|
|
127
|
-
type: 'object',
|
|
128
|
-
properties: { b: { type: 'string' } },
|
|
129
|
-
};
|
|
130
|
-
writeSchema(path.join(tmpDir, 'A'), 'schema-A.json', schemaA);
|
|
131
|
-
writeSchema(path.join(tmpDir, 'B'), 'schema-B.json', schemaB);
|
|
132
|
-
|
|
133
|
-
await expect(validateSchemas(tmpDir)).rejects.toThrow(
|
|
134
|
-
'schema with key or id "duplicate-id" already exists',
|
|
135
|
-
);
|
|
136
52
|
});
|
|
137
53
|
});
|
|
@@ -1,71 +1,66 @@
|
|
|
1
1
|
import { getSingleExampleValue } from './example-helper';
|
|
2
|
+
import mergeJsonSchema from 'json-schema-merge-allof';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const getPrimitivePlaceholder = (type) => {
|
|
5
|
+
switch (type) {
|
|
6
|
+
case 'string':
|
|
7
|
+
return '';
|
|
8
|
+
case 'integer':
|
|
9
|
+
case 'number':
|
|
10
|
+
return 0;
|
|
11
|
+
case 'boolean':
|
|
12
|
+
return false;
|
|
13
|
+
default:
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
if (schema
|
|
16
|
-
return
|
|
18
|
+
const buildExampleFromSchema = (schema) => {
|
|
19
|
+
if (!schema) {
|
|
20
|
+
return undefined;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
|
|
23
|
+
// For choices, default to the first option and recurse.
|
|
24
|
+
const choiceType = schema.oneOf ? 'oneOf' : schema.anyOf ? 'anyOf' : null;
|
|
25
|
+
if (choiceType && schema[choiceType]?.length > 0) {
|
|
26
|
+
const newSchema = { ...schema };
|
|
27
|
+
const choice = newSchema[choiceType][0];
|
|
28
|
+
delete newSchema[choiceType];
|
|
29
|
+
const merged = mergeJsonSchema({ allOf: [newSchema, choice] });
|
|
30
|
+
return buildExampleFromSchema(merged);
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
// If there's an explicit example, use it.
|
|
22
34
|
const exampleValue = getSingleExampleValue(schema);
|
|
23
35
|
if (typeof exampleValue !== 'undefined') {
|
|
24
36
|
return exampleValue;
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
39
|
+
// Determine the type, defaulting to 'object' if properties are present.
|
|
40
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
41
|
+
const inferredType = !type && schema.properties ? 'object' : type;
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
// Build examples based on type.
|
|
44
|
+
if (inferredType === 'object' && schema.properties) {
|
|
45
|
+
const obj = Object.entries(schema.properties).reduce(
|
|
46
|
+
(acc, [key, propSchema]) => {
|
|
36
47
|
const value = buildExampleFromSchema(propSchema);
|
|
37
48
|
if (typeof value !== 'undefined') {
|
|
38
|
-
|
|
49
|
+
acc[key] = value;
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return undefined;
|
|
51
|
+
return acc;
|
|
52
|
+
},
|
|
53
|
+
{},
|
|
54
|
+
);
|
|
55
|
+
return Object.keys(obj).length > 0 ? obj : undefined;
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// Only return an array if the item schema generates a value.
|
|
52
|
-
return typeof itemValue !== 'undefined' ? [itemValue] : undefined;
|
|
53
|
-
}
|
|
54
|
-
return undefined; // No items defined.
|
|
58
|
+
if (inferredType === 'array' && schema.items) {
|
|
59
|
+
const itemValue = buildExampleFromSchema(schema.items);
|
|
60
|
+
return typeof itemValue !== 'undefined' ? [itemValue] : undefined;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
switch (type) {
|
|
59
|
-
case 'string':
|
|
60
|
-
return '';
|
|
61
|
-
case 'integer':
|
|
62
|
-
case 'number':
|
|
63
|
-
return 0;
|
|
64
|
-
case 'boolean':
|
|
65
|
-
return false;
|
|
66
|
-
default:
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
63
|
+
return getPrimitivePlaceholder(inferredType);
|
|
69
64
|
};
|
|
70
65
|
|
|
71
66
|
export default buildExampleFromSchema;
|
package/helpers/file-system.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import loadSchema from './loadSchema';
|
|
4
3
|
|
|
5
4
|
export function createDir(directory) {
|
|
6
5
|
if (!fs.existsSync(directory)) {
|
|
@@ -15,7 +14,7 @@ export function readSchemas(directory) {
|
|
|
15
14
|
|
|
16
15
|
return files.map((file) => {
|
|
17
16
|
const filePath = path.join(directory, file);
|
|
18
|
-
const schema =
|
|
17
|
+
const schema = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
19
18
|
return {
|
|
20
19
|
fileName: file,
|
|
21
20
|
filePath,
|
|
@@ -15,21 +15,6 @@ export function slugify(text) {
|
|
|
15
15
|
.replace(/-+$/, ''); // Trim - from end of text
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function fixRefs(obj, basePath) {
|
|
19
|
-
for (const key in obj) {
|
|
20
|
-
if (
|
|
21
|
-
key === '$ref' &&
|
|
22
|
-
typeof obj[key] === 'string' &&
|
|
23
|
-
!obj[key].startsWith('#') &&
|
|
24
|
-
!obj[key].startsWith('http')
|
|
25
|
-
) {
|
|
26
|
-
obj[key] = path.resolve(basePath, obj[key]);
|
|
27
|
-
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
28
|
-
fixRefs(obj[key], basePath);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
export async function processOneOfSchema(schema, filePath) {
|
|
34
19
|
const processedSchemas = [];
|
|
35
20
|
const choiceType = schema.oneOf ? 'oneOf' : null;
|
|
@@ -1,99 +1,71 @@
|
|
|
1
1
|
import buildExampleFromSchema from './buildExampleFromSchema';
|
|
2
|
+
import mergeJsonSchema from 'json-schema-merge-allof';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* @param {object} rootSchema The complete schema for the event.
|
|
8
|
-
* @returns {object[]} An array of group objects.
|
|
9
|
-
*/
|
|
10
|
-
export function schemaToExamples(rootSchema) {
|
|
11
|
-
const choicePoints = [];
|
|
4
|
+
const findChoicePoints = (subSchema, path = []) => {
|
|
5
|
+
if (!subSchema) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const choiceType = subSchema.oneOf
|
|
10
|
+
? 'oneOf'
|
|
11
|
+
: subSchema.anyOf
|
|
12
|
+
? 'anyOf'
|
|
13
|
+
: null;
|
|
14
|
+
const currentChoice = choiceType ? [{ path, schema: subSchema }] : [];
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
const nestedChoices = subSchema.properties
|
|
17
|
+
? Object.entries(subSchema.properties).flatMap(([key, propSchema]) =>
|
|
18
|
+
findChoicePoints(propSchema, [...path, 'properties', key]),
|
|
19
|
+
)
|
|
20
|
+
: [];
|
|
21
|
+
|
|
22
|
+
return [...currentChoice, ...nestedChoices];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const generateExampleForChoice = (rootSchema, path, option) => {
|
|
26
|
+
const schemaVariant = JSON.parse(JSON.stringify(rootSchema));
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (path.length === 0) {
|
|
29
|
+
delete schemaVariant.oneOf;
|
|
30
|
+
delete schemaVariant.anyOf;
|
|
31
|
+
const newSchemaVariant = mergeJsonSchema({
|
|
32
|
+
allOf: [schemaVariant, option],
|
|
33
|
+
});
|
|
34
|
+
return buildExampleFromSchema(newSchemaVariant);
|
|
35
|
+
} else {
|
|
36
|
+
let parentOfChoice = schemaVariant;
|
|
37
|
+
for (let i = 0; i < path.length; i++) {
|
|
38
|
+
parentOfChoice = parentOfChoice[path[i]];
|
|
32
39
|
}
|
|
40
|
+
delete parentOfChoice.oneOf;
|
|
41
|
+
delete parentOfChoice.anyOf;
|
|
42
|
+
Object.assign(parentOfChoice, option);
|
|
43
|
+
return buildExampleFromSchema(schemaVariant);
|
|
33
44
|
}
|
|
45
|
+
};
|
|
34
46
|
|
|
35
|
-
|
|
47
|
+
export function schemaToExamples(rootSchema) {
|
|
48
|
+
const choicePoints = findChoicePoints(rootSchema);
|
|
36
49
|
|
|
37
|
-
// 2. If no choices are found, generate a single default example
|
|
38
50
|
if (choicePoints.length === 0) {
|
|
39
51
|
const example = buildExampleFromSchema(rootSchema);
|
|
40
52
|
if (example && Object.keys(example).length > 0) {
|
|
41
|
-
// Return in the same group structure for consistency
|
|
42
53
|
return [
|
|
43
|
-
{
|
|
44
|
-
property: 'default',
|
|
45
|
-
options: [{ title: 'Example', example }],
|
|
46
|
-
},
|
|
54
|
+
{ property: 'default', options: [{ title: 'Example', example }] },
|
|
47
55
|
];
|
|
48
56
|
}
|
|
49
57
|
return [];
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
// 3. Map each found choice point to a "group" of examples
|
|
53
60
|
return choicePoints.map(({ path, schema }) => {
|
|
54
61
|
const choiceType = schema.oneOf ? 'oneOf' : 'anyOf';
|
|
55
|
-
const choices = schema[choiceType];
|
|
56
62
|
const propertyName = path.length > 0 ? path[path.length - 1] : 'root';
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// If path is empty, the choice is at the root of the schema.
|
|
64
|
-
if (path.length === 0) {
|
|
65
|
-
// Merge the chosen option's properties into the root properties
|
|
66
|
-
schemaVariant.properties = {
|
|
67
|
-
...schemaVariant.properties,
|
|
68
|
-
...option.properties,
|
|
69
|
-
};
|
|
70
|
-
// Remove the choice block from the root
|
|
71
|
-
delete schemaVariant.oneOf;
|
|
72
|
-
delete schemaVariant.anyOf;
|
|
73
|
-
} else {
|
|
74
|
-
// The choice is nested. Find the parent of the choice block in the copied schema.
|
|
75
|
-
let parentOfChoice = schemaVariant;
|
|
76
|
-
for (let i = 0; i < path.length; i++) {
|
|
77
|
-
parentOfChoice = parentOfChoice[path[i]];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Delete the choice keywords and merge the selected option.
|
|
81
|
-
// This preserves other properties on the parent (like `type` and `description`).
|
|
82
|
-
delete parentOfChoice.oneOf;
|
|
83
|
-
delete parentOfChoice.anyOf;
|
|
84
|
-
Object.assign(parentOfChoice, option);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const example = buildExampleFromSchema(schemaVariant);
|
|
88
|
-
return {
|
|
89
|
-
title: option.title || 'Option',
|
|
90
|
-
example,
|
|
91
|
-
};
|
|
92
|
-
});
|
|
64
|
+
const options = schema[choiceType].map((option) => ({
|
|
65
|
+
title: option.title || 'Option',
|
|
66
|
+
example: generateExampleForChoice(rootSchema, path, option),
|
|
67
|
+
}));
|
|
93
68
|
|
|
94
|
-
return {
|
|
95
|
-
property: propertyName,
|
|
96
|
-
options,
|
|
97
|
-
};
|
|
69
|
+
return { property: propertyName, options };
|
|
98
70
|
});
|
|
99
71
|
}
|