docusaurus-plugin-generate-schema-docs 1.4.0 → 1.5.1
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 +4 -3
- package/validateSchemas.js +70 -55
- package/__tests__/helpers/loadSchema.test.js +0 -20
- package/helpers/loadSchema.js +0 -11
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
3
|
+
import Ajv2019 from 'ajv/dist/2019.js';
|
|
4
|
+
import AjvDraft4 from 'ajv-draft-04';
|
|
5
|
+
import addFormats from 'ajv-formats';
|
|
6
|
+
import ajvKeywords from 'ajv-keywords';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promises as fs } from 'fs';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
|
|
11
|
+
function createAjvInstance(schemas, mainSchema, schemaPath) {
|
|
12
|
+
const schemaVersion = mainSchema?.$schema;
|
|
13
|
+
|
|
14
|
+
const loadSchema = async (uri) => {
|
|
15
|
+
let localPath;
|
|
16
|
+
if (uri.startsWith('http')) {
|
|
17
|
+
const url = new URL(uri);
|
|
18
|
+
const pathFromUrl = url.pathname.startsWith('/schemas/')
|
|
19
|
+
? url.pathname.substring('/schemas/'.length)
|
|
20
|
+
: url.pathname;
|
|
21
|
+
localPath = path.join(schemaPath, pathFromUrl);
|
|
22
|
+
} else {
|
|
23
|
+
localPath = path.resolve(schemaPath, uri);
|
|
24
|
+
}
|
|
25
|
+
const schemaContent = await fs.readFile(localPath, 'utf-8');
|
|
26
|
+
return JSON.parse(schemaContent);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const options = { allErrors: true, schemas: schemas };
|
|
30
|
+
|
|
31
|
+
let ajv;
|
|
32
|
+
if (schemaVersion?.includes('2020-12')) {
|
|
33
|
+
options.strict = false;
|
|
34
|
+
options.loadSchema = loadSchema;
|
|
35
|
+
ajv = new Ajv2020(options);
|
|
36
|
+
} else if (schemaVersion?.includes('2019-09')) {
|
|
37
|
+
options.strict = false;
|
|
38
|
+
options.loadSchema = loadSchema;
|
|
39
|
+
ajv = new Ajv2019(options);
|
|
40
|
+
} else if (schemaVersion?.includes('draft-07')) {
|
|
41
|
+
options.strict = false;
|
|
42
|
+
options.loadSchema = loadSchema;
|
|
43
|
+
ajv = new Ajv(options);
|
|
44
|
+
} else if (schemaVersion?.includes('draft-06')) {
|
|
45
|
+
options.strict = false;
|
|
46
|
+
options.loadSchema = loadSchema;
|
|
47
|
+
ajv = new Ajv(options);
|
|
48
|
+
} else if (schemaVersion?.includes('draft-04')) {
|
|
49
|
+
ajv = new AjvDraft4();
|
|
50
|
+
schemas.forEach((s) => ajv.addSchema(s));
|
|
51
|
+
} else {
|
|
52
|
+
options.strict = false;
|
|
53
|
+
options.loadSchema = loadSchema;
|
|
54
|
+
ajv = new Ajv(options);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addFormats(ajv);
|
|
58
|
+
if (ajv.addKeyword) {
|
|
59
|
+
ajv.addKeyword('x-gtm-clear');
|
|
60
|
+
}
|
|
61
|
+
ajvKeywords(ajv);
|
|
62
|
+
|
|
63
|
+
return ajv;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a validation function for a given set of JSON schemas.
|
|
68
|
+
*
|
|
69
|
+
* @param {object[]} schemas An array of all JSON schemas.
|
|
70
|
+
* @param {object} mainSchema The main JSON schema to validate against.
|
|
71
|
+
* @returns {function(object): {valid: boolean, errors: object[]}} A function that takes data and returns a validation result.
|
|
72
|
+
*/
|
|
73
|
+
export async function createValidator(schemas, mainSchema, schemaPath) {
|
|
74
|
+
if (!mainSchema) {
|
|
75
|
+
mainSchema = schemas;
|
|
76
|
+
schemas = [mainSchema];
|
|
77
|
+
}
|
|
78
|
+
const ajv = createAjvInstance(schemas, mainSchema, schemaPath);
|
|
79
|
+
|
|
80
|
+
let validate;
|
|
81
|
+
if (mainSchema?.$schema?.includes('draft-04')) {
|
|
82
|
+
validate = mainSchema['$id']
|
|
83
|
+
? ajv.getSchema(mainSchema['$id'])
|
|
84
|
+
: ajv.compile(mainSchema);
|
|
85
|
+
} else if (ajv.compileAsync) {
|
|
86
|
+
validate = await ajv.compileAsync(mainSchema);
|
|
87
|
+
} else {
|
|
88
|
+
validate = mainSchema['$id']
|
|
89
|
+
? ajv.getSchema(mainSchema['$id'])
|
|
90
|
+
: ajv.compile(mainSchema);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!validate) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Could not compile schema or find compiled schema for ${
|
|
96
|
+
mainSchema['$id'] || 'main schema'
|
|
97
|
+
}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (data) => {
|
|
102
|
+
const valid = validate(data);
|
|
103
|
+
if (!valid) {
|
|
104
|
+
return { valid: false, errors: validate.errors };
|
|
105
|
+
}
|
|
106
|
+
return { valid: true, errors: [] };
|
|
107
|
+
};
|
|
108
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-plugin-generate-schema-docs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Docusaurus plugin to generate documentation from JSON schemas.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@apidevtools/json-schema-ref-parser": "^15.1.3",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
13
|
+
"json-schema-merge-allof": "^0.8.1",
|
|
14
|
+
"ajv": "^8.17.1",
|
|
15
|
+
"ajv-draft-04": "^1.0.0"
|
|
15
16
|
},
|
|
16
17
|
"publishConfig": {
|
|
17
18
|
"access": "public",
|
package/validateSchemas.js
CHANGED
|
@@ -1,74 +1,89 @@
|
|
|
1
|
+
import { createValidator } from './helpers/validator.js';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import addFormats from 'ajv-formats';
|
|
6
|
-
import processSchema from './helpers/processSchema';
|
|
4
|
+
import processSchema from './helpers/processSchema.js';
|
|
5
|
+
import { schemaToExamples } from './helpers/schemaToExamples.js';
|
|
7
6
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const validateSingleSchema = async (filePath, schemaPath) => {
|
|
8
|
+
const file = path.basename(filePath);
|
|
9
|
+
const errors = [];
|
|
10
|
+
let allValid = true;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const mergedSchema = await processSchema(filePath);
|
|
14
|
+
const exampleGroups = schemaToExamples(mergedSchema);
|
|
15
|
+
const originalSchema = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
16
|
+
const validate = await createValidator([], originalSchema, schemaPath);
|
|
17
|
+
|
|
18
|
+
if (exampleGroups.length === 0) {
|
|
19
|
+
errors.push(`x Schema ${file} does not produce any examples.`);
|
|
20
|
+
return { allValid: false, errors };
|
|
21
|
+
}
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
let fileHasValidExample = false;
|
|
24
|
+
let fileHasAnyExample = false;
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
26
|
+
for (const group of exampleGroups) {
|
|
27
|
+
for (const option of group.options) {
|
|
28
|
+
fileHasAnyExample = true;
|
|
29
|
+
const { example, title } = option;
|
|
30
|
+
|
|
31
|
+
if (!example) {
|
|
32
|
+
errors.push(
|
|
33
|
+
`x Schema ${file} (option: ${title}) does not produce a valid example.`,
|
|
34
|
+
);
|
|
35
|
+
allValid = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = validate(example);
|
|
40
|
+
if (result.valid) {
|
|
41
|
+
fileHasValidExample = true;
|
|
42
|
+
} else {
|
|
43
|
+
errors.push(
|
|
44
|
+
`x Schema ${file} (option: ${title}) example data failed validation:`,
|
|
45
|
+
);
|
|
46
|
+
errors.push(JSON.stringify(result.errors, null, 2));
|
|
47
|
+
allValid = false;
|
|
25
48
|
}
|
|
26
49
|
}
|
|
27
|
-
}
|
|
28
|
-
return allFiles;
|
|
29
|
-
};
|
|
50
|
+
}
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
if (fileHasAnyExample && !fileHasValidExample) {
|
|
53
|
+
errors.push(
|
|
54
|
+
`x Schema ${file} had examples, but none of them were valid.`,
|
|
55
|
+
);
|
|
56
|
+
allValid = false;
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
errors.push(`x Error processing ${file}: ${error.message}`);
|
|
60
|
+
allValid = false;
|
|
36
61
|
}
|
|
37
62
|
|
|
38
|
-
|
|
63
|
+
return { allValid, errors };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const validateSchemas = async (schemaPath) => {
|
|
67
|
+
const topLevelSchemaFiles = fs
|
|
39
68
|
.readdirSync(schemaPath)
|
|
40
69
|
.filter((file) => file.endsWith('.json'));
|
|
41
|
-
let allValid = true;
|
|
42
|
-
for (const file of schemaFiles) {
|
|
43
|
-
const filePath = path.join(schemaPath, file);
|
|
44
70
|
|
|
45
|
-
|
|
71
|
+
const results = await Promise.all(
|
|
72
|
+
topLevelSchemaFiles.map((file) => {
|
|
73
|
+
const filePath = path.join(schemaPath, file);
|
|
74
|
+
return validateSingleSchema(filePath, schemaPath);
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
46
77
|
|
|
47
|
-
|
|
78
|
+
const allSucceeded = results.every((r) => r.allValid);
|
|
48
79
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const originalSchema = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
54
|
-
const validate = ajv.getSchema(originalSchema.$id);
|
|
55
|
-
if (!validate) {
|
|
56
|
-
console.error(
|
|
57
|
-
`x Could not find compiled schema for ${originalSchema.$id}`,
|
|
58
|
-
);
|
|
59
|
-
allValid = false;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (validate(example_data)) {
|
|
63
|
-
console.log(`OK Schema ${file} produced a valid example.`);
|
|
64
|
-
} else {
|
|
65
|
-
console.error(`x Schema ${file} example data failed validation:`);
|
|
66
|
-
console.error(validate.errors);
|
|
67
|
-
allValid = false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
80
|
+
if (!allSucceeded) {
|
|
81
|
+
results.forEach((r) => {
|
|
82
|
+
r.errors.forEach((e) => console.error(e));
|
|
83
|
+
});
|
|
70
84
|
}
|
|
71
|
-
|
|
85
|
+
|
|
86
|
+
return allSucceeded;
|
|
72
87
|
};
|
|
73
88
|
|
|
74
89
|
export default validateSchemas;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @jest-environment node
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import loadSchema from '../../helpers/loadSchema';
|
|
7
|
-
|
|
8
|
-
describe('loadSchema', () => {
|
|
9
|
-
it('should load and parse a JSON file', () => {
|
|
10
|
-
const filePath = path.join(
|
|
11
|
-
__dirname,
|
|
12
|
-
'..',
|
|
13
|
-
'__fixtures__',
|
|
14
|
-
'validateSchemas',
|
|
15
|
-
'circular-schema.json',
|
|
16
|
-
);
|
|
17
|
-
const schema = loadSchema(filePath);
|
|
18
|
-
expect(schema.title).toBe('Circular Schema');
|
|
19
|
-
});
|
|
20
|
-
});
|
package/helpers/loadSchema.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Loads a JSON schema file and returns its content as a JSON object.
|
|
5
|
-
* @param {string} filePath Path to the JSON schema file.
|
|
6
|
-
* @returns {object} The parsed JSON schema.
|
|
7
|
-
*/
|
|
8
|
-
export default function loadSchema(filePath) {
|
|
9
|
-
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
10
|
-
return JSON.parse(rawContent);
|
|
11
|
-
}
|