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.
Files changed (81) hide show
  1. package/README.md +42 -6
  2. package/__tests__/ExampleDataLayer.test.js +78 -155
  3. package/__tests__/__fixtures__/static/schemas/add-to-cart-event.json +4 -15
  4. package/__tests__/__fixtures__/static/schemas/choice-event.json +72 -0
  5. package/__tests__/__fixtures__/static/schemas/components/dataLayer.json +52 -54
  6. package/__tests__/__fixtures__/static/schemas/components/product.json +124 -210
  7. package/__tests__/__fixtures__/static/schemas/nested/child-event.json +10 -0
  8. package/__tests__/__fixtures__/static/schemas/nested/grandchild-a.json +9 -0
  9. package/__tests__/__fixtures__/static/schemas/nested/grandchild-b.json +9 -0
  10. package/__tests__/__fixtures__/static/schemas/nested/parent-event.json +7 -0
  11. package/__tests__/__fixtures__/static/schemas/root-any-of-event.json +34 -0
  12. package/__tests__/__fixtures__/static/schemas/root-choice-event.json +36 -0
  13. package/__tests__/__fixtures__/validateSchemas/circular-schema.json +6 -6
  14. package/__tests__/__fixtures__/validateSchemas/components/referenced.json +9 -7
  15. package/__tests__/__fixtures__/validateSchemas/invalid-example-schema.json +7 -7
  16. package/__tests__/__fixtures__/validateSchemas/main-schema-with-missing-ref.json +7 -7
  17. package/__tests__/__fixtures__/validateSchemas/main-schema-with-ref.json +7 -7
  18. package/__tests__/__fixtures__/validateSchemas/no-example-schema.json +11 -11
  19. package/__tests__/__fixtures__/validateSchemas/schema-A.json +5 -5
  20. package/__tests__/__fixtures__/validateSchemas/schema-B.json +5 -5
  21. package/__tests__/__fixtures__/validateSchemas/valid-schema.json +7 -7
  22. package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/add-to-cart-event.json +44 -0
  23. package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/dataLayer.json +56 -0
  24. package/__tests__/__fixtures_versioned__/static/schemas/1.1.1/components/product.json +125 -0
  25. package/__tests__/__fixtures_versioned__/static/schemas/next/add-to-cart-event.json +44 -0
  26. package/__tests__/__fixtures_versioned__/static/schemas/next/components/dataLayer.json +56 -0
  27. package/__tests__/__fixtures_versioned__/static/schemas/next/components/product.json +125 -0
  28. package/__tests__/__fixtures_versioned__/versions.json +1 -0
  29. package/__tests__/__snapshots__/ExampleDataLayer.test.js.snap +117 -0
  30. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +92 -0
  31. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +113 -15
  32. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +53 -0
  33. package/__tests__/components/FoldableRows.test.js +330 -0
  34. package/__tests__/components/PropertiesTable.test.js +31 -14
  35. package/__tests__/components/PropertyRow.test.js +471 -51
  36. package/__tests__/components/SchemaJsonViewer.test.js +23 -19
  37. package/__tests__/components/SchemaRows.test.js +96 -66
  38. package/__tests__/components/SchemaViewer.test.js +34 -17
  39. package/__tests__/components/TableHeader.test.js +12 -12
  40. package/__tests__/generateEventDocs.nested.test.js +80 -0
  41. package/__tests__/generateEventDocs.test.js +77 -71
  42. package/__tests__/generateEventDocs.versioned.test.js +69 -0
  43. package/__tests__/helpers/buildExampleFromSchema.test.js +160 -160
  44. package/__tests__/helpers/file-system.test.js +44 -0
  45. package/__tests__/helpers/getConstraints.test.js +48 -48
  46. package/__tests__/helpers/loadSchema.test.js +11 -5
  47. package/__tests__/helpers/path-helpers.test.js +34 -0
  48. package/__tests__/helpers/processSchema.test.js +42 -22
  49. package/__tests__/helpers/schema-processing.test.js +82 -0
  50. package/__tests__/helpers/schemaToExamples.test.js +56 -0
  51. package/__tests__/helpers/schemaToTableData.filtering.test.js +65 -0
  52. package/__tests__/helpers/schemaToTableData.hierarchicalLines.test.js +539 -0
  53. package/__tests__/helpers/schemaToTableData.test.js +222 -0
  54. package/__tests__/helpers/update-schema-ids.test.js +107 -0
  55. package/__tests__/update-schema-ids.test.js +39 -0
  56. package/__tests__/validateSchemas.test.js +125 -88
  57. package/components/ExampleDataLayer.js +59 -28
  58. package/components/FoldableRows.js +164 -0
  59. package/components/PropertiesTable.js +10 -7
  60. package/components/PropertyRow.js +169 -60
  61. package/components/SchemaJsonViewer.js +6 -6
  62. package/components/SchemaRows.css +236 -14
  63. package/components/SchemaRows.js +24 -41
  64. package/components/SchemaViewer.js +19 -13
  65. package/components/TableHeader.js +12 -12
  66. package/generateEventDocs.js +141 -61
  67. package/helpers/buildExampleFromSchema.js +58 -72
  68. package/helpers/choice-index-template.js +22 -0
  69. package/helpers/file-system.js +32 -0
  70. package/helpers/getConstraints.js +43 -44
  71. package/helpers/loadSchema.js +2 -2
  72. package/helpers/path-helpers.js +22 -0
  73. package/helpers/processSchema.js +19 -19
  74. package/helpers/{mdx-template.js → schema-doc-template.js} +12 -12
  75. package/helpers/schema-processing.js +75 -0
  76. package/helpers/schemaToExamples.js +99 -0
  77. package/helpers/schemaToTableData.js +311 -0
  78. package/helpers/update-schema-ids.js +47 -0
  79. package/index.js +143 -54
  80. package/package.json +1 -1
  81. 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
- const buildValue = (prop) => {
3
- if (!prop) return undefined;
10
+ if (!schema) return undefined;
4
11
 
5
- // 1. Prefer explicit examples or constants if available
6
- if (prop.examples && prop.examples.length) return prop.examples[0];
7
- if (prop.const !== undefined) return prop.const;
8
- if (prop.default !== undefined) return prop.default;
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
- const type = Array.isArray(prop.type) ? prop.type[0] : prop.type;
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
- if (type === 'object')
13
- {
14
- if (prop.properties)
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
- // If the object ends up having no keys, consider it "empty" and return undefined
27
- if (Object.keys(obj).length === 0) return undefined;
28
- return obj;
29
- }
30
- // Object with no properties defined
31
- return undefined;
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
- if (type === 'array')
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
- // Otherwise, build from properties
67
- if (schema && schema.properties)
68
- {
69
- const out = {};
70
- Object.entries(schema.properties).forEach(([k, p]) => {
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
- // 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}`,
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
- // Special-cased constraints
20
- pattern: (val) => `pattern: /${val}/`,
21
- uniqueItems: (val) => (val ? 'uniqueItems: true' : null),
22
- additionalProperties: (val) => (val === false ? 'additionalProperties: false' : null),
23
- propertyNames: (val) => `propertyNames: ${JSON.stringify(val)}`,
24
- dependentRequired: (val) => {
25
- const deps = Object.entries(val).map(([key, depVal]) => `${key} -> [${depVal.join(', ')}]`).join('; ');
26
- return `dependentRequired: ${deps}`;
27
- },
28
- contains: (val) => `contains: ${JSON.stringify(val)}`,
29
- enum: (val) => `enum: [${val.join(', ')}]`,
30
- const: (val) => `const: ${JSON.stringify(val)}`,
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
- const constraints = [];
35
- if (isReq)
36
- {
37
- constraints.push('required');
38
- }
37
+ const constraints = [];
38
+ if (isReq) {
39
+ constraints.push('required');
40
+ }
39
41
 
40
- for (const keyword in constraintHandlers)
41
- {
42
- if (prop[keyword] !== undefined)
43
- {
44
- const handler = constraintHandlers[keyword];
45
- const result = handler(prop[keyword]);
46
- if (result)
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
- return constraints;
50
+ }
51
+ return constraints;
53
52
  };
@@ -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
- const rawContent = fs.readFileSync(filePath, 'utf-8');
10
- return JSON.parse(rawContent);
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
+ }
@@ -1,5 +1,5 @@
1
- import $RefParser from "@apidevtools/json-schema-ref-parser";
2
- import mergeJsonSchema from "json-schema-merge-allof";
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
- // 1. Bundle all external references into a single, self-contained schema
13
- const bundledSchema = await $RefParser.bundle(filePath, {
14
- mutateInputSchema: false,
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
- // 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
- });
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
- // Then merge allOf properties
25
- const mergedSchema = mergeJsonSchema(dereferencedSchema, {
26
- resolvers: {
27
- defaultResolver: mergeJsonSchema.options.resolvers.title
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
- return mergedSchema;
31
+ return mergedSchema;
32
32
  }
@@ -1,20 +1,20 @@
1
1
  export default function MdxTemplate(data) {
2
- const {
3
- schema,
4
- mergedSchema,
5
- baseEditUrl,
6
- file,
7
- topPartialImport,
8
- bottomPartialImport,
9
- topPartialComponent,
10
- bottomPartialComponent
11
- } = data;
2
+ const {
3
+ schema,
4
+ mergedSchema,
5
+ editUrl,
6
+ file,
7
+ topPartialImport,
8
+ bottomPartialImport,
9
+ topPartialComponent,
10
+ bottomPartialComponent,
11
+ } = data;
12
12
 
13
- return `---
13
+ return `---
14
14
  title: ${schema.title}
15
15
  description: ${JSON.stringify(schema.description)}
16
16
  sidebar_label: ${schema.title}
17
- custom_edit_url: ${baseEditUrl}/demo/static/schemas/${file}
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
+ }