docusaurus-plugin-generate-schema-docs 1.2.0 → 1.3.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/README.md +42 -6
- package/__tests__/ExampleDataLayer.test.js +78 -155
- package/__tests__/__fixtures__/static/schemas/add-to-cart-event.json +4 -15
- package/__tests__/__fixtures__/static/schemas/choice-event.json +72 -0
- package/__tests__/__fixtures__/static/schemas/components/dataLayer.json +52 -54
- package/__tests__/__fixtures__/static/schemas/components/product.json +124 -210
- package/__tests__/__fixtures__/static/schemas/nested/child-event.json +10 -0
- package/__tests__/__fixtures__/static/schemas/nested/grandchild-a.json +9 -0
- package/__tests__/__fixtures__/static/schemas/nested/grandchild-b.json +9 -0
- package/__tests__/__fixtures__/static/schemas/nested/parent-event.json +7 -0
- package/__tests__/__fixtures__/static/schemas/root-any-of-event.json +34 -0
- package/__tests__/__fixtures__/static/schemas/root-choice-event.json +36 -0
- package/__tests__/__fixtures__/validateSchemas/circular-schema.json +6 -6
- package/__tests__/__fixtures__/validateSchemas/components/referenced.json +9 -7
- package/__tests__/__fixtures__/validateSchemas/invalid-example-schema.json +7 -7
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-missing-ref.json +7 -7
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-ref.json +7 -7
- package/__tests__/__fixtures__/validateSchemas/no-example-schema.json +11 -11
- package/__tests__/__fixtures__/validateSchemas/schema-A.json +5 -5
- package/__tests__/__fixtures__/validateSchemas/schema-B.json +5 -5
- package/__tests__/__fixtures__/validateSchemas/valid-schema.json +7 -7
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/add-to-cart-event.json +44 -0
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/dataLayer.json +56 -0
- package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/product.json +125 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/add-to-cart-event.json +44 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/components/dataLayer.json +56 -0
- package/__tests__/__fixtures_versioned__/static/schemas/next/components/product.json +125 -0
- package/__tests__/__fixtures_versioned__/versions.json +1 -0
- package/__tests__/__snapshots__/ExampleDataLayer.test.js.snap +117 -0
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +92 -0
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +113 -15
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +53 -0
- package/__tests__/components/FoldableRows.test.js +330 -0
- package/__tests__/components/PropertiesTable.test.js +31 -14
- package/__tests__/components/PropertyRow.test.js +471 -51
- package/__tests__/components/SchemaJsonViewer.test.js +23 -19
- package/__tests__/components/SchemaRows.test.js +96 -66
- package/__tests__/components/SchemaViewer.test.js +34 -17
- package/__tests__/components/TableHeader.test.js +12 -12
- package/__tests__/generateEventDocs.nested.test.js +80 -0
- package/__tests__/generateEventDocs.test.js +77 -71
- package/__tests__/generateEventDocs.versioned.test.js +69 -0
- package/__tests__/helpers/buildExampleFromSchema.test.js +160 -160
- package/__tests__/helpers/file-system.test.js +44 -0
- package/__tests__/helpers/getConstraints.test.js +48 -48
- package/__tests__/helpers/loadSchema.test.js +11 -5
- package/__tests__/helpers/path-helpers.test.js +34 -0
- package/__tests__/helpers/processSchema.test.js +42 -22
- package/__tests__/helpers/schema-processing.test.js +82 -0
- package/__tests__/helpers/schemaToExamples.test.js +56 -0
- package/__tests__/helpers/schemaToTableData.filtering.test.js +65 -0
- package/__tests__/helpers/schemaToTableData.hierarchicalLines.test.js +539 -0
- package/__tests__/helpers/schemaToTableData.test.js +222 -0
- package/__tests__/helpers/update-schema-ids.test.js +107 -0
- package/__tests__/update-schema-ids.test.js +39 -0
- package/__tests__/validateSchemas.test.js +125 -88
- package/components/ExampleDataLayer.js +59 -28
- package/components/FoldableRows.js +164 -0
- package/components/PropertiesTable.js +10 -7
- package/components/PropertyRow.js +169 -60
- package/components/SchemaJsonViewer.js +6 -6
- package/components/SchemaRows.css +236 -14
- package/components/SchemaRows.js +24 -41
- package/components/SchemaViewer.js +19 -13
- package/components/TableHeader.js +12 -12
- package/generateEventDocs.js +141 -61
- package/helpers/buildExampleFromSchema.js +58 -72
- package/helpers/choice-index-template.js +22 -0
- package/helpers/file-system.js +32 -0
- package/helpers/getConstraints.js +43 -44
- package/helpers/loadSchema.js +2 -2
- package/helpers/path-helpers.js +22 -0
- package/helpers/processSchema.js +19 -19
- package/helpers/{mdx-template.js → schema-doc-template.js} +12 -12
- package/helpers/schema-processing.js +75 -0
- package/helpers/schemaToExamples.js +99 -0
- package/helpers/schemaToTableData.js +311 -0
- package/helpers/update-schema-ids.js +47 -0
- package/index.js +143 -54
- package/package.json +1 -1
- package/validateSchemas.js +54 -71
|
@@ -1,83 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively builds a single, default example object from a JSON schema.
|
|
3
|
+
* It prefers explicit examples, consts, or defaults. For choices (`oneOf`/`anyOf`),
|
|
4
|
+
* it defaults to the first option.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} schema The schema to build an example from.
|
|
7
|
+
* @returns {object|undefined} The generated example.
|
|
8
|
+
*/
|
|
1
9
|
const buildExampleFromSchema = (schema) => {
|
|
2
|
-
|
|
3
|
-
if (!prop) return undefined;
|
|
10
|
+
if (!schema) return undefined;
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
// For choices, default to the first option.
|
|
13
|
+
if (schema.oneOf?.length > 0) {
|
|
14
|
+
return buildExampleFromSchema(schema.oneOf[0]);
|
|
15
|
+
}
|
|
16
|
+
if (schema.anyOf?.length > 0) {
|
|
17
|
+
return buildExampleFromSchema(schema.anyOf[0]);
|
|
18
|
+
}
|
|
9
19
|
|
|
10
|
-
|
|
20
|
+
// Prefer const, explicit examples, or default values.
|
|
21
|
+
if (typeof schema.const !== 'undefined') return schema.const;
|
|
22
|
+
if (schema.examples?.length > 0) return schema.examples[0];
|
|
23
|
+
if (typeof schema.default !== 'undefined') return schema.default;
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const obj = {};
|
|
17
|
-
Object.entries(prop.properties).forEach(([k, v]) => {
|
|
18
|
-
const val = buildValue(v);
|
|
19
|
-
// Only add the property if it has a valid value (not undefined)
|
|
20
|
-
if (val !== undefined)
|
|
21
|
-
{
|
|
22
|
-
obj[k] = val;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
+
let type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
26
|
+
if (!type && schema.properties) {
|
|
27
|
+
type = 'object';
|
|
28
|
+
}
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (type === 'object') {
|
|
31
|
+
if (schema.properties) {
|
|
32
|
+
const obj = {};
|
|
33
|
+
Object.entries(schema.properties).forEach(([key, propSchema]) => {
|
|
34
|
+
const value = buildExampleFromSchema(propSchema);
|
|
35
|
+
if (typeof value !== 'undefined') {
|
|
36
|
+
obj[key] = value;
|
|
32
37
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (prop.items)
|
|
37
|
-
{
|
|
38
|
-
const itemVal = buildValue(prop.items);
|
|
39
|
-
// If the inner item is valid, return it as an array
|
|
40
|
-
if (itemVal !== undefined)
|
|
41
|
-
{
|
|
42
|
-
return [itemVal];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// Empty array or array of undefined items
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
switch (type)
|
|
50
|
-
{
|
|
51
|
-
case 'string': return '';
|
|
52
|
-
case 'integer':
|
|
53
|
-
case 'number': return 0;
|
|
54
|
-
case 'boolean': return false;
|
|
55
|
-
default: return undefined;
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// If the schema provides a top-level examples array with an object, prefer it
|
|
60
|
-
if (schema && schema.examples && schema.examples.length)
|
|
61
|
-
{
|
|
62
|
-
const first = schema.examples[0];
|
|
63
|
-
if (typeof first === 'object' && first !== null) return first;
|
|
38
|
+
});
|
|
39
|
+
// Return undefined for objects that end up empty.
|
|
40
|
+
return Object.keys(obj).length > 0 ? obj : undefined;
|
|
64
41
|
}
|
|
42
|
+
// No properties defined, treat as an empty object (which we filter out).
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
65
45
|
|
|
66
|
-
|
|
67
|
-
if (schema
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const val = buildValue(p);
|
|
72
|
-
// Only add top-level keys if they are not undefined
|
|
73
|
-
if (val !== undefined)
|
|
74
|
-
{
|
|
75
|
-
out[k] = val;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
return out;
|
|
46
|
+
if (type === 'array') {
|
|
47
|
+
if (schema.items) {
|
|
48
|
+
const itemValue = buildExampleFromSchema(schema.items);
|
|
49
|
+
// Only return an array if the item schema generates a value.
|
|
50
|
+
return typeof itemValue !== 'undefined' ? [itemValue] : undefined;
|
|
79
51
|
}
|
|
52
|
+
return undefined; // No items defined.
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Return placeholder values for primitive types if no other value is found.
|
|
56
|
+
switch (type) {
|
|
57
|
+
case 'string':
|
|
58
|
+
return '';
|
|
59
|
+
case 'integer':
|
|
60
|
+
case 'number':
|
|
61
|
+
return 0;
|
|
62
|
+
case 'boolean':
|
|
63
|
+
return false;
|
|
64
|
+
default:
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
80
68
|
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
69
|
export default buildExampleFromSchema;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default function ChoiceIndexTemplate(data) {
|
|
2
|
+
const { schema, processedOptions } = data;
|
|
3
|
+
|
|
4
|
+
return `---
|
|
5
|
+
title: ${schema.title}
|
|
6
|
+
description: "${schema.description}"
|
|
7
|
+
---
|
|
8
|
+
import SchemaJsonViewer from '@theme/SchemaJsonViewer';
|
|
9
|
+
|
|
10
|
+
# ${schema.title}
|
|
11
|
+
|
|
12
|
+
${schema.description}
|
|
13
|
+
|
|
14
|
+
Please select one of the following options:
|
|
15
|
+
|
|
16
|
+
${processedOptions
|
|
17
|
+
.map((option) => `- [${option.schema.title}](./${option.slug})`)
|
|
18
|
+
.join('\n')}
|
|
19
|
+
|
|
20
|
+
<SchemaJsonViewer schema={${JSON.stringify(schema)}} />
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import loadSchema from './loadSchema';
|
|
4
|
+
|
|
5
|
+
export function createDir(directory) {
|
|
6
|
+
if (!fs.existsSync(directory)) {
|
|
7
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function readSchemas(directory) {
|
|
12
|
+
const files = fs
|
|
13
|
+
.readdirSync(directory)
|
|
14
|
+
.filter((file) => file.endsWith('.json'));
|
|
15
|
+
|
|
16
|
+
return files.map((file) => {
|
|
17
|
+
const filePath = path.join(directory, file);
|
|
18
|
+
const schema = loadSchema(filePath);
|
|
19
|
+
return {
|
|
20
|
+
fileName: file,
|
|
21
|
+
filePath,
|
|
22
|
+
schema,
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function writeDoc(outputDir, fileName, content) {
|
|
28
|
+
fs.writeFileSync(path.join(outputDir, fileName), content);
|
|
29
|
+
console.log(
|
|
30
|
+
`✅ Generated ${path.relative(process.cwd(), path.join(outputDir, fileName))}`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -1,53 +1,52 @@
|
|
|
1
1
|
// A list of JSON Schema keywords that have simple key-value constraints.
|
|
2
2
|
const constraintHandlers = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
3
|
+
// Simple key-value constraints
|
|
4
|
+
minLength: (val) => `minLength: ${val}`,
|
|
5
|
+
maxLength: (val) => `maxLength: ${val}`,
|
|
6
|
+
minimum: (val) => `minimum: ${val}`,
|
|
7
|
+
maximum: (val) => `maximum: ${val}`,
|
|
8
|
+
exclusiveMinimum: (val) => `exclusiveMinimum: ${val}`,
|
|
9
|
+
exclusiveMaximum: (val) => `exclusiveMaximum: ${val}`,
|
|
10
|
+
minItems: (val) => `minItems: ${val}`,
|
|
11
|
+
maxItems: (val) => `maxItems: ${val}`,
|
|
12
|
+
minProperties: (val) => `minProperties: ${val}`,
|
|
13
|
+
maxProperties: (val) => `maxProperties: ${val}`,
|
|
14
|
+
multipleOf: (val) => `multipleOf: ${val}`,
|
|
15
|
+
format: (val) => `format: ${val}`,
|
|
16
|
+
minContains: (val) => `minContains: ${val}`,
|
|
17
|
+
maxContains: (val) => `maxContains: ${val}`,
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
// Special-cased constraints
|
|
20
|
+
pattern: (val) => `pattern: /${val}/`,
|
|
21
|
+
uniqueItems: (val) => (val ? 'uniqueItems: true' : null),
|
|
22
|
+
additionalProperties: (val) =>
|
|
23
|
+
val === false ? 'additionalProperties: false' : null,
|
|
24
|
+
propertyNames: (val) => `propertyNames: ${JSON.stringify(val)}`,
|
|
25
|
+
dependentRequired: (val) => {
|
|
26
|
+
const deps = Object.entries(val)
|
|
27
|
+
.map(([key, depVal]) => `${key} -> [${depVal.join(', ')}]`)
|
|
28
|
+
.join('; ');
|
|
29
|
+
return `dependentRequired: ${deps}`;
|
|
30
|
+
},
|
|
31
|
+
contains: (val) => `contains: ${JSON.stringify(val)}`,
|
|
32
|
+
enum: (val) => `enum: [${val.join(', ')}]`,
|
|
33
|
+
const: (val) => `const: ${JSON.stringify(val)}`,
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
export const getConstraints = (prop, isReq) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
37
|
+
const constraints = [];
|
|
38
|
+
if (isReq) {
|
|
39
|
+
constraints.push('required');
|
|
40
|
+
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
constraints.push(result);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
42
|
+
for (const keyword in constraintHandlers) {
|
|
43
|
+
if (prop[keyword] !== undefined) {
|
|
44
|
+
const handler = constraintHandlers[keyword];
|
|
45
|
+
const result = handler(prop[keyword]);
|
|
46
|
+
if (result) {
|
|
47
|
+
constraints.push(result);
|
|
48
|
+
}
|
|
51
49
|
}
|
|
52
|
-
|
|
50
|
+
}
|
|
51
|
+
return constraints;
|
|
53
52
|
};
|
package/helpers/loadSchema.js
CHANGED
|
@@ -6,6 +6,6 @@ import fs from 'fs';
|
|
|
6
6
|
* @returns {object} The parsed JSON schema.
|
|
7
7
|
*/
|
|
8
8
|
export default function loadSchema(filePath) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
10
|
+
return JSON.parse(rawContent);
|
|
11
11
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export function getPathsForVersion(version, siteDir) {
|
|
4
|
+
let schemaDir;
|
|
5
|
+
let outputDir;
|
|
6
|
+
|
|
7
|
+
if (version) {
|
|
8
|
+
if (version !== 'current') {
|
|
9
|
+
schemaDir = path.join(siteDir, 'static/schemas', version);
|
|
10
|
+
outputDir = path.join(siteDir, 'versioned_docs', `version-${version}`);
|
|
11
|
+
} else {
|
|
12
|
+
schemaDir = path.join(siteDir, 'static/schemas', 'next');
|
|
13
|
+
outputDir = path.join(siteDir, 'docs');
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
// Non-versioned
|
|
17
|
+
schemaDir = path.join(siteDir, 'static/schemas');
|
|
18
|
+
outputDir = path.join(siteDir, 'docs');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { schemaDir, outputDir };
|
|
22
|
+
}
|
package/helpers/processSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import $RefParser from
|
|
2
|
-
import mergeJsonSchema from
|
|
1
|
+
import $RefParser from '@apidevtools/json-schema-ref-parser';
|
|
2
|
+
import mergeJsonSchema from 'json-schema-merge-allof';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Processes a JSON schema file by bundling external references,
|
|
@@ -9,24 +9,24 @@ import mergeJsonSchema from "json-schema-merge-allof";
|
|
|
9
9
|
* @returns {Promise<object>} The processed (merged) schema.
|
|
10
10
|
*/
|
|
11
11
|
export default async function processSchema(filePath) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// 1. Bundle all external references into a single, self-contained schema
|
|
13
|
+
const bundledSchema = await $RefParser.bundle(filePath, {
|
|
14
|
+
mutateInputSchema: false,
|
|
15
|
+
});
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// 2. Dereference the bundled schema to resolve internal refs for allOf merging
|
|
18
|
+
const dereferencedSchema = await $RefParser.dereference(bundledSchema, {
|
|
19
|
+
dereference: {
|
|
20
|
+
circular: 'ignore', // Keep recursive parts as $refs
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
// Then merge allOf properties
|
|
25
|
+
const mergedSchema = mergeJsonSchema(dereferencedSchema, {
|
|
26
|
+
resolvers: {
|
|
27
|
+
defaultResolver: mergeJsonSchema.options.resolvers.title,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
return mergedSchema;
|
|
32
32
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
export default function MdxTemplate(data) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
const {
|
|
3
|
+
schema,
|
|
4
|
+
mergedSchema,
|
|
5
|
+
editUrl,
|
|
6
|
+
file,
|
|
7
|
+
topPartialImport,
|
|
8
|
+
bottomPartialImport,
|
|
9
|
+
topPartialComponent,
|
|
10
|
+
bottomPartialComponent,
|
|
11
|
+
} = data;
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
return `---
|
|
14
14
|
title: ${schema.title}
|
|
15
15
|
description: ${JSON.stringify(schema.description)}
|
|
16
16
|
sidebar_label: ${schema.title}
|
|
17
|
-
custom_edit_url: ${
|
|
17
|
+
custom_edit_url: ${editUrl}
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
import SchemaViewer from '@theme/SchemaViewer';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import processSchema from './processSchema.js';
|
|
3
|
+
|
|
4
|
+
export function slugify(text) {
|
|
5
|
+
if (!text) {
|
|
6
|
+
return 'option';
|
|
7
|
+
}
|
|
8
|
+
return text
|
|
9
|
+
.toString()
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/\s+/g, '-') // Replace spaces with -
|
|
12
|
+
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
|
|
13
|
+
.replace(/\-\-+/g, '-') // Replace multiple - with single -
|
|
14
|
+
.replace(/^-+/, '') // Trim - from start of text
|
|
15
|
+
.replace(/-+$/, ''); // Trim - from end of text
|
|
16
|
+
}
|
|
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
|
+
export async function processOneOfSchema(schema, filePath) {
|
|
34
|
+
const processedSchemas = [];
|
|
35
|
+
const choiceType = schema.oneOf ? 'oneOf' : null;
|
|
36
|
+
|
|
37
|
+
if (choiceType) {
|
|
38
|
+
const parentWithoutChoice = { ...schema };
|
|
39
|
+
delete parentWithoutChoice[choiceType];
|
|
40
|
+
|
|
41
|
+
for (const option of schema[choiceType]) {
|
|
42
|
+
let resolvedOption = option;
|
|
43
|
+
if (option.$ref && !option.$ref.startsWith('#')) {
|
|
44
|
+
const refPath = path.resolve(path.dirname(filePath), option.$ref);
|
|
45
|
+
resolvedOption = await processSchema(refPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Merge the parent schema with the resolved option schema
|
|
49
|
+
const newSchema = { ...parentWithoutChoice, ...resolvedOption };
|
|
50
|
+
newSchema.properties = {
|
|
51
|
+
...parentWithoutChoice.properties,
|
|
52
|
+
...resolvedOption.properties,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
let slug;
|
|
56
|
+
const hadId = resolvedOption.$id && resolvedOption.$id.endsWith('.json');
|
|
57
|
+
if (hadId) {
|
|
58
|
+
slug = path.basename(resolvedOption.$id, '.json');
|
|
59
|
+
} else {
|
|
60
|
+
slug = slugify(newSchema.title);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!hadId) {
|
|
64
|
+
newSchema.$id = `${schema.$id}#${slug}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
processedSchemas.push({
|
|
68
|
+
slug,
|
|
69
|
+
schema: newSchema,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return processedSchemas;
|
|
75
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import buildExampleFromSchema from './buildExampleFromSchema';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This is the main helper that generates a list of complete example objects,
|
|
5
|
+
* grouped by the property that contains the choice.
|
|
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 = [];
|
|
12
|
+
|
|
13
|
+
// 1. Find all choice points in the schema recursively
|
|
14
|
+
function findChoices(subSchema, path = []) {
|
|
15
|
+
if (!subSchema) return;
|
|
16
|
+
|
|
17
|
+
// First, check for choices at the current schema level
|
|
18
|
+
const choiceType = subSchema.oneOf
|
|
19
|
+
? 'oneOf'
|
|
20
|
+
: subSchema.anyOf
|
|
21
|
+
? 'anyOf'
|
|
22
|
+
: null;
|
|
23
|
+
if (choiceType) {
|
|
24
|
+
choicePoints.push({ path, schema: subSchema });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Then, recurse into any nested properties
|
|
28
|
+
if (subSchema.properties) {
|
|
29
|
+
for (const [key, propSchema] of Object.entries(subSchema.properties)) {
|
|
30
|
+
findChoices(propSchema, [...path, 'properties', key]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
findChoices(rootSchema);
|
|
36
|
+
|
|
37
|
+
// 2. If no choices are found, generate a single default example
|
|
38
|
+
if (choicePoints.length === 0) {
|
|
39
|
+
const example = buildExampleFromSchema(rootSchema);
|
|
40
|
+
if (example && Object.keys(example).length > 0) {
|
|
41
|
+
// Return in the same group structure for consistency
|
|
42
|
+
return [
|
|
43
|
+
{
|
|
44
|
+
property: 'default',
|
|
45
|
+
options: [{ title: 'Example', example }],
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Map each found choice point to a "group" of examples
|
|
53
|
+
return choicePoints.map(({ path, schema }) => {
|
|
54
|
+
const choiceType = schema.oneOf ? 'oneOf' : 'anyOf';
|
|
55
|
+
const choices = schema[choiceType];
|
|
56
|
+
const propertyName = path.length > 0 ? path[path.length - 1] : 'root';
|
|
57
|
+
|
|
58
|
+
// For each option within the choice, generate a complete example
|
|
59
|
+
const options = choices.map((option) => {
|
|
60
|
+
// Create a deep copy of the root schema to modify for this specific example
|
|
61
|
+
const schemaVariant = JSON.parse(JSON.stringify(rootSchema));
|
|
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
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
property: propertyName,
|
|
96
|
+
options,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|