Skip to content

Commit 7e18265

Browse files
committed
Add custom rendering logic for this pattern
1 parent 172973d commit 7e18265

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import React from 'react';
2+
import type { JSONSchema } from "json-schema-typed/draft-2020-12"
3+
import Tabs from "@theme/Tabs";
4+
import TabItem from "@theme/TabItem";
5+
6+
import { CreateNodes } from "@theme-original/JSONSchemaViewer/components"
7+
import {
8+
SchemaHierarchyContextProvider,
9+
useSchemaHierarchyContext,
10+
} from "@theme-original/JSONSchemaViewer/contexts"
11+
12+
export interface Inclusions {
13+
propertyNames: string[];
14+
schemasByPropertyName: {
15+
[propertyName: string]: {
16+
schema: Exclude<JSONSchema, boolean>;
17+
index: number;
18+
}
19+
}
20+
}
21+
22+
export interface InclusiveRequiredPropertiesSchemaProps extends Inclusions {
23+
}
24+
25+
export default function InclusiveRequiredPropertiesSchema({
26+
propertyNames,
27+
schemasByPropertyName
28+
}: InclusiveRequiredPropertiesSchemaProps): JSX.Element {
29+
const { jsonPointer: currentJsonPointer, level: currentLevel } =
30+
useSchemaHierarchyContext()
31+
32+
return (
33+
<div>
34+
<hr />
35+
<span className="badge badge--info">independently-inclusive required properties</span>&nbsp;
36+
This object may specify any of the following:
37+
<ul>
38+
{propertyNames.map((propertyName, index) =>
39+
<li key={index}><code>{propertyName}</code></li>
40+
)}
41+
</ul>
42+
43+
Depending on which required properties are used, the following
44+
corresponding sub-schemas may apply:
45+
46+
<Tabs>{
47+
Object.entries(schemasByPropertyName)
48+
.map(([propertyName, { schema, index }]) => (
49+
<TabItem
50+
key={propertyName}
51+
label={("title" in schema && typeof schema.title === "string" && schema.title) || propertyName}
52+
value={propertyName}
53+
>
54+
<SchemaHierarchyContextProvider
55+
value={{
56+
level: currentLevel + 1,
57+
jsonPointer: `${currentJsonPointer}/allOf/${index + 1}/then`
58+
}}
59+
>
60+
<CreateNodes schema={schema} />
61+
</SchemaHierarchyContextProvider>
62+
</TabItem>
63+
))
64+
}</Tabs>
65+
66+
</div>
67+
);
68+
}
69+
70+
const isIfThen = (
71+
schema: JSONSchema
72+
): schema is JSONSchema & {
73+
if: JSONSchema;
74+
then: JSONSchema;
75+
} =>
76+
typeof schema !== "boolean" &&
77+
"if" in schema && "then" in schema;
78+
79+
const isSingleRequiredProperty = (
80+
schema: JSONSchema
81+
): schema is JSONSchema & {
82+
required: [string]
83+
} =>
84+
typeof schema !== "boolean" &&
85+
"required" in schema && schema.required?.length === 1;
86+
87+
export function detectInclusiveRequiredProperties(schema: {
88+
allOf: JSONSchema[]
89+
}): Inclusions | undefined {
90+
const { allOf } = schema;
91+
92+
// every schema in the `allOf` should be structured as an if/then/else,
93+
// where every `if` schema checks only for a single, unique required property
94+
95+
if (!allOf.every(isIfThen)) {
96+
return;
97+
}
98+
99+
const ifs = allOf.map(({ if: if_ }) => if_);
100+
101+
if (!ifs.every(isSingleRequiredProperty)) {
102+
return;
103+
}
104+
105+
const propertyNames = [...new Set(
106+
ifs.map(({ required: [propertyName] }) => propertyName)
107+
)];
108+
109+
// check that property names are unique
110+
if (propertyNames.length !== ifs.length) {
111+
console.debug("property names are not unique");
112+
return;
113+
}
114+
115+
116+
const schemasByPropertyName = (
117+
allOf as (JSONSchema & { if: { required: [string] }; then: Exclude<JSONSchema, boolean>; })[]
118+
)
119+
.map(
120+
(
121+
{
122+
if: { required: [propertyName] },
123+
then
124+
},
125+
index
126+
) => ({
127+
[propertyName]: {
128+
schema: then,
129+
index
130+
}
131+
})
132+
)
133+
.reduce((a, b) => ({ ...a, ...b }), {});
134+
135+
return {
136+
propertyNames,
137+
schemasByPropertyName
138+
};
139+
}
140+

packages/web/src/theme/JSONSchemaViewer/JSONSchemaElements/schemaComposition/allOfSchema.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import ExclusiveRequiredPropertiesSchema, {
1010
detectExclusiveRequiredProperties
1111
} from "./ExclusiveRequiredPropertiesSchema";
1212

13+
import InclusiveRequiredPropertiesSchema, {
14+
detectInclusiveRequiredProperties
15+
} from "./InclusiveRequiredPropertiesSchema";
16+
1317
export default function allOfSchemaWrapper(props: {
1418
schema: Exclude<JSONSchema, boolean> & { allOf: JSONSchema[]; }
1519
}) {
@@ -25,6 +29,11 @@ export default function allOfSchemaWrapper(props: {
2529
return <ExclusiveRequiredPropertiesSchema {...exclusions} />
2630
}
2731

32+
const inclusions = detectInclusiveRequiredProperties(schema);
33+
if (inclusions) {
34+
return <InclusiveRequiredPropertiesSchema {...inclusions} />
35+
}
36+
2837
return (
2938
<>
3039
<AllOfSchema {...props} />

0 commit comments

Comments
 (0)