Skip to content

Commit 2cc7142

Browse files
authored
Merge pull request #294 from contentauth/schema-ref-defaults
Update SchemaReference component to render default values properly
2 parents c56cfe3 + 6c7abb1 commit 2cc7142

File tree

5 files changed

+497
-1108
lines changed

5 files changed

+497
-1108
lines changed

src/components/SchemaReference.js

Lines changed: 120 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const slugify = (text) =>
88

99
const isObject = (v) => v && typeof v === 'object' && !Array.isArray(v);
1010

11+
const refToDefName = (ref) => {
12+
if (typeof ref !== 'string') return null;
13+
if (ref.startsWith('#/$defs/')) return ref.slice('#/$defs/'.length);
14+
if (ref.startsWith('#/definitions/'))
15+
return ref.slice('#/definitions/'.length);
16+
return null;
17+
};
18+
1119
function formatType(cellSchema) {
1220
if (!cellSchema) return 'N/A';
1321

@@ -206,7 +214,13 @@ function Markdown({ text }) {
206214
return <span dangerouslySetInnerHTML={{ __html: markdownToHtml(text) }} />;
207215
}
208216

209-
function PropertiesTable({ title, description, properties, required }) {
217+
function PropertiesTable({
218+
title,
219+
description,
220+
properties,
221+
required,
222+
defaults,
223+
}) {
210224
if (!properties || Object.keys(properties).length === 0) return null;
211225

212226
return (
@@ -242,10 +256,42 @@ function PropertiesTable({ title, description, properties, required }) {
242256
const isRequired = Array.isArray(required)
243257
? required.includes(name)
244258
: false;
245-
const defaultValue =
259+
const explicitDefault =
246260
schema && Object.prototype.hasOwnProperty.call(schema, 'default')
247-
? JSON.stringify(schema.default)
248-
: 'N/A';
261+
? schema.default
262+
: undefined;
263+
const inferredDefault =
264+
defaults && Object.prototype.hasOwnProperty.call(defaults, name)
265+
? defaults[name]
266+
: undefined;
267+
const chosenDefault =
268+
explicitDefault !== undefined
269+
? explicitDefault
270+
: inferredDefault !== undefined
271+
? inferredDefault
272+
: undefined;
273+
274+
// Link to the referenced definition for non-primitive defaults (objects/arrays) when possible
275+
let defaultCell;
276+
if (chosenDefault === undefined) {
277+
defaultCell = 'N/A';
278+
} else if (
279+
typeof chosenDefault === 'object' &&
280+
chosenDefault !== null
281+
) {
282+
const defName = schema ? refToDefName(schema.$ref) : null;
283+
if (defName) {
284+
defaultCell = (
285+
<>
286+
See <a href={`#${slugify(defName)}`}>{defName}</a>
287+
</>
288+
);
289+
} else {
290+
defaultCell = JSON.stringify(chosenDefault);
291+
}
292+
} else {
293+
defaultCell = JSON.stringify(chosenDefault);
294+
}
249295

250296
return (
251297
<tr key={name}>
@@ -265,7 +311,7 @@ function PropertiesTable({ title, description, properties, required }) {
265311
<td className="manifest-ref-table">
266312
{isRequired ? 'YES' : 'NO'}
267313
</td>
268-
<td className="manifest-ref-table">{defaultValue}</td>
314+
<td className="manifest-ref-table">{defaultCell}</td>
269315
</tr>
270316
);
271317
})}
@@ -402,6 +448,7 @@ function DefinitionSection({ name, schema }) {
402448
<PropertiesTable
403449
properties={schema.properties || {}}
404450
required={schema.required || []}
451+
defaults={schema.__inferredDefaults || undefined}
405452
/>
406453
) : (
407454
// Fallback simple table for non-object schemas
@@ -445,6 +492,54 @@ export default function SchemaReference({ schemaUrl }) {
445492
const [error, setError] = useState(null);
446493
const [loading, setLoading] = useState(true);
447494

495+
const buildDefDefaults = (root) => {
496+
if (!root || !isObject(root)) return {};
497+
const defs = root.$defs || root.definitions || {};
498+
const defDefaults = {};
499+
500+
const feed = (defName, defSchema, value) => {
501+
if (!defName || !defSchema) return;
502+
if (!isObject(value)) {
503+
// Record scalar/array as the default value of the referenced node when used as a property.
504+
// For nested object defaults we handle below.
505+
return;
506+
}
507+
const props = (defSchema && defSchema.properties) || {};
508+
if (!isObject(props)) return;
509+
if (!defDefaults[defName]) defDefaults[defName] = {};
510+
for (const [propName, propSchema] of Object.entries(props)) {
511+
if (!Object.prototype.hasOwnProperty.call(value, propName)) continue;
512+
const propValue = value[propName];
513+
// Always record the value for the property at this level for display
514+
defDefaults[defName][propName] = propValue;
515+
// If the property itself references another definition and the default value is an object,
516+
// propagate deeper so nested definition sections can show their own inferred defaults.
517+
const subRefName = propSchema && refToDefName(propSchema.$ref);
518+
if (subRefName) {
519+
const subDefSchema = defs[subRefName];
520+
if (subDefSchema) feed(subRefName, subDefSchema, propValue);
521+
}
522+
}
523+
};
524+
525+
const rootProps = (root && root.properties) || {};
526+
for (const [, propSchema] of Object.entries(rootProps)) {
527+
const defName = propSchema && refToDefName(propSchema.$ref);
528+
if (!defName) continue;
529+
const defSchema = defs[defName];
530+
if (!defSchema) continue;
531+
if (
532+
propSchema &&
533+
Object.prototype.hasOwnProperty.call(propSchema, 'default')
534+
) {
535+
const defaultValue = propSchema.default;
536+
feed(defName, defSchema, defaultValue);
537+
}
538+
}
539+
540+
return defDefaults;
541+
};
542+
448543
useEffect(() => {
449544
let cancelled = false;
450545
async function run() {
@@ -467,6 +562,11 @@ export default function SchemaReference({ schemaUrl }) {
467562
};
468563
}, [schemaUrl]);
469564

565+
const inferredDefaults = useMemo(
566+
() => buildDefDefaults(schema || {}),
567+
[schema],
568+
);
569+
470570
if (loading) return <p>Loading schema…</p>;
471571
if (error) return <p style={{ color: 'red' }}>{error}</p>;
472572
if (!schema) return <p>No schema loaded.</p>;
@@ -496,9 +596,21 @@ export default function SchemaReference({ schemaUrl }) {
496596

497597
<DefinitionsTOC defs={defs} />
498598

499-
{Object.entries(defs).map(([name, defSchema]) => (
500-
<DefinitionSection key={name} name={name} schema={defSchema} />
501-
))}
599+
{Object.entries(defs).map(([name, defSchema]) => {
600+
const schemaWithDefaults =
601+
(defSchema && {
602+
...defSchema,
603+
__inferredDefaults: inferredDefaults[name],
604+
}) ||
605+
defSchema;
606+
return (
607+
<DefinitionSection
608+
key={name}
609+
name={name}
610+
schema={schemaWithDefaults}
611+
/>
612+
);
613+
})}
502614
</div>
503615
);
504616
}

0 commit comments

Comments
 (0)