docusaurus-plugin-generate-schema-docs 1.5.4 → 1.7.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__/static/schemas/battle-test-event.json +771 -0
- package/__tests__/__fixtures__/static/schemas/conditional-event.json +52 -0
- package/__tests__/__fixtures__/static/schemas/nested-conditional-event.json +50 -0
- package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +3 -2
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +4 -2
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +3 -2
- package/__tests__/components/ConditionalRows.test.js +150 -0
- package/__tests__/components/ConnectorLines.visualRegression.test.js +93 -0
- package/__tests__/components/FoldableRows.test.js +7 -4
- package/__tests__/components/SchemaRows.test.js +31 -0
- package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +7 -0
- package/__tests__/generateEventDocs.anchor.test.js +7 -0
- package/__tests__/generateEventDocs.nested.test.js +7 -0
- package/__tests__/generateEventDocs.partials.test.js +134 -0
- package/__tests__/generateEventDocs.test.js +7 -0
- package/__tests__/helpers/buildExampleFromSchema.test.js +49 -0
- package/__tests__/helpers/schemaToExamples.test.js +75 -0
- package/__tests__/helpers/schemaToTableData.battleTest.test.js +704 -0
- package/__tests__/helpers/schemaToTableData.hierarchicalLines.test.js +190 -7
- package/__tests__/helpers/schemaToTableData.test.js +263 -2
- package/__tests__/helpers/validator.test.js +6 -6
- package/components/ConditionalRows.js +156 -0
- package/components/FoldableRows.js +88 -61
- package/components/PropertiesTable.js +1 -1
- package/components/PropertyRow.js +24 -8
- package/components/SchemaRows.css +115 -0
- package/components/SchemaRows.js +31 -4
- package/generateEventDocs.js +55 -37
- package/helpers/buildExampleFromSchema.js +11 -0
- package/helpers/choice-index-template.js +2 -1
- package/helpers/continuingLinesStyle.js +169 -0
- package/helpers/schema-doc-template.js +2 -5
- package/helpers/schema-processing.js +3 -0
- package/helpers/schemaToExamples.js +75 -2
- package/helpers/schemaToTableData.js +252 -26
- package/helpers/update-schema-ids.js +3 -3
- package/helpers/validator.js +7 -19
- package/package.json +3 -2
package/generateEventDocs.js
CHANGED
|
@@ -7,6 +7,20 @@ import SchemaDocTemplate from './helpers/schema-doc-template.js';
|
|
|
7
7
|
import ChoiceIndexTemplate from './helpers/choice-index-template.js';
|
|
8
8
|
import processSchema from './helpers/processSchema.js';
|
|
9
9
|
|
|
10
|
+
function buildEditUrl(organizationName, projectName, siteDir, filePath) {
|
|
11
|
+
const baseEditUrl = `https://github.com/${organizationName}/${projectName}/edit/main`;
|
|
12
|
+
return `${baseEditUrl}/${path.relative(path.join(siteDir, '..'), filePath)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolvePartial(partialPath, relativePartialsDir, componentPrefix) {
|
|
16
|
+
if (!fs.existsSync(partialPath)) return { import: '', component: '' };
|
|
17
|
+
const fileName = path.basename(partialPath);
|
|
18
|
+
return {
|
|
19
|
+
import: `import ${componentPrefix} from '@site/${relativePartialsDir}/${fileName}';`,
|
|
20
|
+
component: `<${componentPrefix} />`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
async function generateAndWriteDoc(
|
|
11
25
|
filePath,
|
|
12
26
|
schema,
|
|
@@ -14,45 +28,45 @@ async function generateAndWriteDoc(
|
|
|
14
28
|
outputDir,
|
|
15
29
|
options,
|
|
16
30
|
alreadyMergedSchema = null,
|
|
31
|
+
editFilePath = null,
|
|
17
32
|
) {
|
|
18
|
-
const { organizationName, projectName, siteDir, dataLayerName } =
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
const { organizationName, projectName, siteDir, dataLayerName, version } =
|
|
34
|
+
options;
|
|
35
|
+
|
|
36
|
+
const { outputDir: versionOutputDir } = getPathsForVersion(version, siteDir);
|
|
37
|
+
const PARTIALS_DIR = path.join(versionOutputDir, 'partials');
|
|
38
|
+
const relativePartialsDir = path.relative(siteDir, PARTIALS_DIR);
|
|
21
39
|
|
|
22
40
|
const mergedSchema = alreadyMergedSchema || (await processSchema(filePath));
|
|
23
41
|
|
|
24
42
|
// Check for partials
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const editUrl = `${baseEditUrl}/${path.relative(
|
|
43
|
-
path.join(siteDir, '..'),
|
|
44
|
-
filePath,
|
|
45
|
-
)}`;
|
|
43
|
+
const top = resolvePartial(
|
|
44
|
+
path.join(PARTIALS_DIR, `_${eventName}.mdx`),
|
|
45
|
+
relativePartialsDir,
|
|
46
|
+
'TopPartial',
|
|
47
|
+
);
|
|
48
|
+
const bottom = resolvePartial(
|
|
49
|
+
path.join(PARTIALS_DIR, `_${eventName}_bottom.mdx`),
|
|
50
|
+
relativePartialsDir,
|
|
51
|
+
'BottomPartial',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const editUrl = buildEditUrl(
|
|
55
|
+
organizationName,
|
|
56
|
+
projectName,
|
|
57
|
+
siteDir,
|
|
58
|
+
editFilePath || filePath,
|
|
59
|
+
);
|
|
46
60
|
|
|
47
61
|
const mdxContent = SchemaDocTemplate({
|
|
48
62
|
schema,
|
|
49
63
|
mergedSchema,
|
|
50
64
|
editUrl,
|
|
51
65
|
file: path.basename(filePath),
|
|
52
|
-
topPartialImport,
|
|
53
|
-
bottomPartialImport,
|
|
54
|
-
topPartialComponent,
|
|
55
|
-
bottomPartialComponent,
|
|
66
|
+
topPartialImport: top.import,
|
|
67
|
+
bottomPartialImport: bottom.import,
|
|
68
|
+
topPartialComponent: top.component,
|
|
69
|
+
bottomPartialComponent: bottom.component,
|
|
56
70
|
dataLayerName,
|
|
57
71
|
});
|
|
58
72
|
|
|
@@ -67,6 +81,14 @@ async function generateOneOfDocs(
|
|
|
67
81
|
outputDir,
|
|
68
82
|
options,
|
|
69
83
|
) {
|
|
84
|
+
const { organizationName, projectName, siteDir } = options;
|
|
85
|
+
const editUrl = buildEditUrl(
|
|
86
|
+
organizationName,
|
|
87
|
+
projectName,
|
|
88
|
+
siteDir,
|
|
89
|
+
filePath,
|
|
90
|
+
);
|
|
91
|
+
|
|
70
92
|
const eventOutputDir = path.join(outputDir, eventName);
|
|
71
93
|
createDir(eventOutputDir);
|
|
72
94
|
|
|
@@ -75,39 +97,35 @@ async function generateOneOfDocs(
|
|
|
75
97
|
const indexPageContent = ChoiceIndexTemplate({
|
|
76
98
|
schema,
|
|
77
99
|
processedOptions: processed,
|
|
100
|
+
editUrl,
|
|
78
101
|
});
|
|
79
102
|
writeDoc(eventOutputDir, 'index.mdx', indexPageContent);
|
|
80
103
|
|
|
81
104
|
for (const [
|
|
82
105
|
index,
|
|
83
|
-
{ slug, schema: processedSchema },
|
|
106
|
+
{ slug, schema: processedSchema, sourceFilePath },
|
|
84
107
|
] of processed.entries()) {
|
|
85
108
|
const subChoiceType = processedSchema.oneOf ? 'oneOf' : null;
|
|
86
109
|
const prefixedSlug = `${(index + 1).toString().padStart(2, '0')}-${slug}`;
|
|
87
110
|
|
|
88
111
|
if (subChoiceType) {
|
|
89
|
-
const tempFilePath = path.join(eventOutputDir, `${slug}.json`);
|
|
90
|
-
fs.writeFileSync(tempFilePath, JSON.stringify(processedSchema, null, 2));
|
|
91
112
|
await generateOneOfDocs(
|
|
92
113
|
prefixedSlug,
|
|
93
114
|
processedSchema,
|
|
94
|
-
|
|
115
|
+
sourceFilePath || filePath,
|
|
95
116
|
eventOutputDir,
|
|
96
117
|
options,
|
|
97
118
|
);
|
|
98
|
-
fs.unlinkSync(tempFilePath);
|
|
99
119
|
} else {
|
|
100
|
-
const tempFilePath = path.join(eventOutputDir, `${prefixedSlug}.json`);
|
|
101
|
-
fs.writeFileSync(tempFilePath, JSON.stringify(processedSchema, null, 2));
|
|
102
120
|
await generateAndWriteDoc(
|
|
103
|
-
|
|
121
|
+
`${prefixedSlug}.json`,
|
|
104
122
|
processedSchema,
|
|
105
123
|
slug,
|
|
106
124
|
eventOutputDir,
|
|
107
125
|
options,
|
|
108
126
|
processedSchema,
|
|
127
|
+
sourceFilePath || filePath,
|
|
109
128
|
);
|
|
110
|
-
fs.unlinkSync(tempFilePath);
|
|
111
129
|
}
|
|
112
130
|
}
|
|
113
131
|
}
|
|
@@ -30,6 +30,17 @@ const buildExampleFromSchema = (schema) => {
|
|
|
30
30
|
return buildExampleFromSchema(merged);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// For conditionals, default to the 'then' branch and recurse.
|
|
34
|
+
if (schema.if && schema.then) {
|
|
35
|
+
const newSchema = { ...schema };
|
|
36
|
+
const thenBranch = newSchema.then;
|
|
37
|
+
delete newSchema.if;
|
|
38
|
+
delete newSchema.then;
|
|
39
|
+
delete newSchema.else;
|
|
40
|
+
const merged = mergeJsonSchema({ allOf: [newSchema, thenBranch] });
|
|
41
|
+
return buildExampleFromSchema(merged);
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
// If there's an explicit example, use it.
|
|
34
45
|
const exampleValue = getSingleExampleValue(schema);
|
|
35
46
|
if (typeof exampleValue !== 'undefined') {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export default function ChoiceIndexTemplate(data) {
|
|
2
|
-
const { schema, processedOptions } = data;
|
|
2
|
+
const { schema, processedOptions, editUrl } = data;
|
|
3
3
|
|
|
4
4
|
return `---
|
|
5
5
|
title: ${schema.title}
|
|
6
6
|
description: "${schema.description}"
|
|
7
|
+
custom_edit_url: ${editUrl}
|
|
7
8
|
---
|
|
8
9
|
import SchemaJsonViewer from '@theme/SchemaJsonViewer';
|
|
9
10
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colors for group bracket lines, indexed by bracketIndex.
|
|
3
|
+
* Uses Docusaurus CSS custom properties so they adapt to light/dark themes.
|
|
4
|
+
*/
|
|
5
|
+
const BRACKET_COLORS = [
|
|
6
|
+
'var(--ifm-color-info)',
|
|
7
|
+
'var(--ifm-color-warning)',
|
|
8
|
+
'var(--ifm-color-success)',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the right-offset (in rem) for a bracket line.
|
|
13
|
+
* Brackets are positioned from the right edge of the cell so they appear
|
|
14
|
+
* on the right side of the table, away from the tree connector lines.
|
|
15
|
+
*
|
|
16
|
+
* B=0 → 0.50 rem from right
|
|
17
|
+
* B=1 → 0.75 rem from right
|
|
18
|
+
* B=2 → 1.00 rem from right
|
|
19
|
+
*/
|
|
20
|
+
export const getBracketPosition = (bracketIndex) => 0.5 + bracketIndex * 0.25;
|
|
21
|
+
|
|
22
|
+
export const getBracketColor = (bracketIndex) =>
|
|
23
|
+
BRACKET_COLORS[bracketIndex % BRACKET_COLORS.length];
|
|
24
|
+
|
|
25
|
+
/** Width of horizontal cap lines in pixels */
|
|
26
|
+
const CAP_WIDTH = 10;
|
|
27
|
+
|
|
28
|
+
/** Inset from cell edge for cap lines (so they aren't hidden by table borders) */
|
|
29
|
+
const CAP_INSET = 6;
|
|
30
|
+
|
|
31
|
+
/** Helper to create a bracket key for Set lookups */
|
|
32
|
+
const bracketKey = (b) => `${b.level}:${b.bracketIndex}`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates inline styles for bracket lines positioned on the right side.
|
|
36
|
+
* Supports optional horizontal "cap" lines at the top and/or bottom of
|
|
37
|
+
* brackets to visually delineate where a bracket group starts and ends.
|
|
38
|
+
*
|
|
39
|
+
* @param {Array<{level: number, bracketIndex: number}>} groupBrackets - Active bracket groups
|
|
40
|
+
* @param {object} [caps] - Optional cap configuration
|
|
41
|
+
* @param {Array<{level: number, bracketIndex: number}>} [caps.starting] - Brackets needing a top cap
|
|
42
|
+
* @param {Array<{level: number, bracketIndex: number}>} [caps.ending] - Brackets needing a bottom cap
|
|
43
|
+
* @returns {object} Style object with background gradients
|
|
44
|
+
*/
|
|
45
|
+
export const getBracketLinesStyle = (groupBrackets = [], caps = {}) => {
|
|
46
|
+
if (groupBrackets.length === 0) return {};
|
|
47
|
+
|
|
48
|
+
const startingKeys = new Set((caps.starting || []).map(bracketKey));
|
|
49
|
+
const endingKeys = new Set((caps.ending || []).map(bracketKey));
|
|
50
|
+
|
|
51
|
+
const gradients = [];
|
|
52
|
+
const sizes = [];
|
|
53
|
+
const positions = [];
|
|
54
|
+
|
|
55
|
+
groupBrackets.forEach((bracket) => {
|
|
56
|
+
const { bracketIndex } = bracket;
|
|
57
|
+
const pos = getBracketPosition(bracketIndex);
|
|
58
|
+
const color = getBracketColor(bracketIndex);
|
|
59
|
+
const key = bracketKey(bracket);
|
|
60
|
+
const isStarting = startingKeys.has(key);
|
|
61
|
+
const isEnding = endingKeys.has(key);
|
|
62
|
+
|
|
63
|
+
// Vertical line — shortened when caps are present so it doesn't bleed past them
|
|
64
|
+
const topInset = isStarting ? CAP_INSET : 0;
|
|
65
|
+
const bottomInset = isEnding ? CAP_INSET : 0;
|
|
66
|
+
gradients.push(`linear-gradient(${color}, ${color})`);
|
|
67
|
+
sizes.push(`1px calc(100% - ${topInset + bottomInset}px)`);
|
|
68
|
+
positions.push(`right ${pos}rem top ${topInset}px`);
|
|
69
|
+
|
|
70
|
+
// Horizontal cap at the top (bracket start) — inset from cell edge
|
|
71
|
+
if (isStarting) {
|
|
72
|
+
const capOffset = (CAP_WIDTH - 1) / 2; // center cap on the vertical line
|
|
73
|
+
gradients.push(`linear-gradient(${color}, ${color})`);
|
|
74
|
+
sizes.push(`${CAP_WIDTH}px 1px`);
|
|
75
|
+
positions.push(
|
|
76
|
+
`right calc(${pos}rem - ${capOffset}px) top ${CAP_INSET}px`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Horizontal cap at the bottom (bracket end) — inset from cell edge
|
|
81
|
+
if (isEnding) {
|
|
82
|
+
const capOffset = (CAP_WIDTH - 1) / 2;
|
|
83
|
+
gradients.push(`linear-gradient(${color}, ${color})`);
|
|
84
|
+
sizes.push(`${CAP_WIDTH}px 1px`);
|
|
85
|
+
positions.push(
|
|
86
|
+
`right calc(${pos}rem - ${capOffset}px) bottom ${CAP_INSET}px`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
backgroundImage: gradients.join(', '),
|
|
93
|
+
backgroundSize: sizes.join(', '),
|
|
94
|
+
backgroundPosition: positions.join(', '),
|
|
95
|
+
backgroundRepeat: 'no-repeat',
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Merges two background-gradient style objects into one.
|
|
101
|
+
* @param {object} style1 - First style object
|
|
102
|
+
* @param {object} style2 - Second style object
|
|
103
|
+
* @returns {object} Merged style object
|
|
104
|
+
*/
|
|
105
|
+
export const mergeBackgroundStyles = (style1, style2) => {
|
|
106
|
+
if (!style2.backgroundImage) return style1;
|
|
107
|
+
if (!style1.backgroundImage) return { ...style1, ...style2 };
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...style1,
|
|
111
|
+
backgroundImage: `${style1.backgroundImage}, ${style2.backgroundImage}`,
|
|
112
|
+
backgroundSize: `${style1.backgroundSize}, ${style2.backgroundSize}`,
|
|
113
|
+
backgroundPosition: `${style1.backgroundPosition}, ${style2.backgroundPosition}`,
|
|
114
|
+
backgroundRepeat: 'no-repeat',
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Generates inline styles for continuing hierarchical lines through a row.
|
|
120
|
+
* Only handles tree connector lines (left side). Bracket lines are separate.
|
|
121
|
+
* @param {number[]} continuingLevels - Array of ancestor levels that need lines
|
|
122
|
+
* @param {number} level - Current level of the row
|
|
123
|
+
* @returns {object} Style object with background gradients
|
|
124
|
+
*/
|
|
125
|
+
export const getContinuingLinesStyle = (continuingLevels = [], level = 0) => {
|
|
126
|
+
const getLevelPosition = (lvl) => lvl * 1.25 + 0.5;
|
|
127
|
+
|
|
128
|
+
const allGradients = [];
|
|
129
|
+
const allSizes = [];
|
|
130
|
+
const allPositions = [];
|
|
131
|
+
|
|
132
|
+
// Draw continuing lines for all ancestor levels
|
|
133
|
+
continuingLevels.forEach((lvl) => {
|
|
134
|
+
const pos = getLevelPosition(lvl);
|
|
135
|
+
allGradients.push(
|
|
136
|
+
'linear-gradient(var(--ifm-table-border-color), var(--ifm-table-border-color))',
|
|
137
|
+
);
|
|
138
|
+
allSizes.push('1px 100%');
|
|
139
|
+
allPositions.push(`${pos}rem top`);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Also draw the line for the immediate parent level (level - 1) if level > 0
|
|
143
|
+
// This connects the rows to their parent property
|
|
144
|
+
if (level > 0) {
|
|
145
|
+
const parentPos = getLevelPosition(level - 1);
|
|
146
|
+
if (!continuingLevels.includes(level - 1)) {
|
|
147
|
+
allGradients.push(
|
|
148
|
+
'linear-gradient(var(--ifm-table-border-color), var(--ifm-table-border-color))',
|
|
149
|
+
);
|
|
150
|
+
allSizes.push('1px 100%');
|
|
151
|
+
allPositions.push(`${parentPos}rem top`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Calculate indentation based on level
|
|
156
|
+
const paddingLeft = `${level * 1.25 + 0.5}rem`;
|
|
157
|
+
|
|
158
|
+
if (allGradients.length === 0) {
|
|
159
|
+
return { paddingLeft };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
paddingLeft,
|
|
164
|
+
backgroundImage: allGradients.join(', '),
|
|
165
|
+
backgroundSize: allSizes.join(', '),
|
|
166
|
+
backgroundPosition: allPositions.join(', '),
|
|
167
|
+
backgroundRepeat: 'no-repeat',
|
|
168
|
+
};
|
|
169
|
+
};
|
|
@@ -8,6 +8,7 @@ export default function MdxTemplate(data) {
|
|
|
8
8
|
bottomPartialImport,
|
|
9
9
|
topPartialComponent,
|
|
10
10
|
bottomPartialComponent,
|
|
11
|
+
dataLayerName,
|
|
11
12
|
} = data;
|
|
12
13
|
|
|
13
14
|
return `---
|
|
@@ -30,11 +31,7 @@ ${topPartialComponent}
|
|
|
30
31
|
|
|
31
32
|
<SchemaViewer
|
|
32
33
|
schema={${JSON.stringify(mergedSchema)}}
|
|
33
|
-
${
|
|
34
|
-
data.dataLayerName && data.dataLayerName !== 'undefined'
|
|
35
|
-
? ` dataLayerName={'${data.dataLayerName}'}`
|
|
36
|
-
: ''
|
|
37
|
-
}
|
|
34
|
+
${dataLayerName ? ` dataLayerName={'${dataLayerName}'}` : ''}
|
|
38
35
|
/>
|
|
39
36
|
<SchemaJsonViewer schema={${JSON.stringify(schema)}} />
|
|
40
37
|
|
|
@@ -25,8 +25,10 @@ export async function processOneOfSchema(schema, filePath) {
|
|
|
25
25
|
|
|
26
26
|
for (const option of schema[choiceType]) {
|
|
27
27
|
let resolvedOption = option;
|
|
28
|
+
let sourceFilePath = null;
|
|
28
29
|
if (option.$ref && !option.$ref.startsWith('#')) {
|
|
29
30
|
const refPath = path.resolve(path.dirname(filePath), option.$ref);
|
|
31
|
+
sourceFilePath = refPath;
|
|
30
32
|
resolvedOption = await processSchema(refPath);
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -54,6 +56,7 @@ export async function processOneOfSchema(schema, filePath) {
|
|
|
54
56
|
processedSchemas.push({
|
|
55
57
|
slug,
|
|
56
58
|
schema: newSchema,
|
|
59
|
+
sourceFilePath,
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -22,6 +22,25 @@ const findChoicePoints = (subSchema, path = []) => {
|
|
|
22
22
|
return [...currentChoice, ...nestedChoices];
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
const findConditionalPoints = (subSchema, path = []) => {
|
|
26
|
+
if (!subSchema) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const currentConditional =
|
|
31
|
+
subSchema.if && (subSchema.then || subSchema.else)
|
|
32
|
+
? [{ path, schema: subSchema }]
|
|
33
|
+
: [];
|
|
34
|
+
|
|
35
|
+
const nestedConditionals = subSchema.properties
|
|
36
|
+
? Object.entries(subSchema.properties).flatMap(([key, propSchema]) =>
|
|
37
|
+
findConditionalPoints(propSchema, [...path, 'properties', key]),
|
|
38
|
+
)
|
|
39
|
+
: [];
|
|
40
|
+
|
|
41
|
+
return [...currentConditional, ...nestedConditionals];
|
|
42
|
+
};
|
|
43
|
+
|
|
25
44
|
const generateExampleForChoice = (rootSchema, path, option) => {
|
|
26
45
|
const schemaVariant = JSON.parse(JSON.stringify(rootSchema));
|
|
27
46
|
|
|
@@ -44,10 +63,43 @@ const generateExampleForChoice = (rootSchema, path, option) => {
|
|
|
44
63
|
}
|
|
45
64
|
};
|
|
46
65
|
|
|
66
|
+
const generateConditionalExample = (rootSchema, path, branch) => {
|
|
67
|
+
const schemaVariant = JSON.parse(JSON.stringify(rootSchema));
|
|
68
|
+
|
|
69
|
+
if (path.length === 0) {
|
|
70
|
+
const branchSchema = schemaVariant[branch];
|
|
71
|
+
delete schemaVariant.if;
|
|
72
|
+
delete schemaVariant.then;
|
|
73
|
+
delete schemaVariant.else;
|
|
74
|
+
if (branchSchema) {
|
|
75
|
+
return buildExampleFromSchema(
|
|
76
|
+
mergeJsonSchema({ allOf: [schemaVariant, branchSchema] }),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return buildExampleFromSchema(schemaVariant);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let target = schemaVariant;
|
|
83
|
+
for (const segment of path) {
|
|
84
|
+
target = target[segment];
|
|
85
|
+
}
|
|
86
|
+
const branchSchema = target[branch];
|
|
87
|
+
delete target.if;
|
|
88
|
+
delete target.then;
|
|
89
|
+
delete target.else;
|
|
90
|
+
if (branchSchema) {
|
|
91
|
+
const merged = mergeJsonSchema({ allOf: [target, branchSchema] });
|
|
92
|
+
Object.keys(target).forEach((k) => delete target[k]);
|
|
93
|
+
Object.assign(target, merged);
|
|
94
|
+
}
|
|
95
|
+
return buildExampleFromSchema(schemaVariant);
|
|
96
|
+
};
|
|
97
|
+
|
|
47
98
|
export function schemaToExamples(rootSchema) {
|
|
48
99
|
const choicePoints = findChoicePoints(rootSchema);
|
|
100
|
+
const conditionalPoints = findConditionalPoints(rootSchema);
|
|
49
101
|
|
|
50
|
-
if (choicePoints.length === 0) {
|
|
102
|
+
if (choicePoints.length === 0 && conditionalPoints.length === 0) {
|
|
51
103
|
const example = buildExampleFromSchema(rootSchema);
|
|
52
104
|
if (example && Object.keys(example).length > 0) {
|
|
53
105
|
return [
|
|
@@ -57,7 +109,7 @@ export function schemaToExamples(rootSchema) {
|
|
|
57
109
|
return [];
|
|
58
110
|
}
|
|
59
111
|
|
|
60
|
-
|
|
112
|
+
const choiceExamples = choicePoints.map(({ path, schema }) => {
|
|
61
113
|
const choiceType = schema.oneOf ? 'oneOf' : 'anyOf';
|
|
62
114
|
const propertyName = path.length > 0 ? path[path.length - 1] : 'root';
|
|
63
115
|
|
|
@@ -68,4 +120,25 @@ export function schemaToExamples(rootSchema) {
|
|
|
68
120
|
|
|
69
121
|
return { property: propertyName, options };
|
|
70
122
|
});
|
|
123
|
+
|
|
124
|
+
const conditionalExamples = conditionalPoints.map(({ path, schema }) => {
|
|
125
|
+
const options = [];
|
|
126
|
+
|
|
127
|
+
if (schema.then) {
|
|
128
|
+
options.push({
|
|
129
|
+
title: 'When condition is met',
|
|
130
|
+
example: generateConditionalExample(rootSchema, path, 'then'),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (schema.else) {
|
|
134
|
+
options.push({
|
|
135
|
+
title: 'When condition is not met',
|
|
136
|
+
example: generateConditionalExample(rootSchema, path, 'else'),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { property: 'conditional', options };
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return [...choiceExamples, ...conditionalExamples];
|
|
71
144
|
}
|