Skip to content

Commit e2aaaa8

Browse files
captbaritonefacebook-github-bot
authored andcommitted
Make schema def rendering more robust (#5036)
Summary: In b208fed we upgraded schemars which changed how the json schema for the compiler config is represented. This uncovered gaps in our documentation renderer. Specifically, we had hard coded the assumption that the definitions would live on the `.defintitions` property of the schema. The new version of schemars puts them under `$defs` which aligns better with json schema spec. This diff makes `$defs` the default assumed location for definitions, but lets the parent pass in definitions from another location. This diff also starts the process of using versioned schemas for our versioned docs. As part of this I pulled in the version of the config schema from the commit that added the v20 docs. So, now as our current config schema changes, the v20 docs will still reflect the config as it was at the v20 release. This allows us to pressure test the docs rendering, ensuring they can render both the old and new representations. Pull Request resolved: #5036 Reviewed By: cfsmp3 Differential Revision: D78595366 Pulled By: captbaritone fbshipit-source-id: 589b320847859b9196b82e473669026d0c899a4b
1 parent 8caf938 commit e2aaaa8

File tree

4 files changed

+1868
-56
lines changed

4 files changed

+1868
-56
lines changed

website/docs/getting-started/compiler-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ If you need more advanced options of the Relay Compiler Config, the exhaustive f
2222
Install the [Relay VSCode extension](../editor-support.md) to get autocomplete, hover tips, and type checking for the options in your Relay config.
2323
:::
2424

25-
<CompilerConfig schema={schema} />
25+
<CompilerConfig schema={schema} definitions={schema.$defs} />

website/src/compiler-config/CompilerConfig.js

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,49 @@ import {useMemo} from 'react';
2424
* them at that point.
2525
*/
2626

27-
export default function CompilerConfig({schema}) {
27+
export default function CompilerConfig({schema, definitions}) {
2828
const indirections = useIndirections(schema);
29-
return <Schema schema={schema} indirections={indirections} />;
29+
return (
30+
<Schema
31+
schema={schema}
32+
indirections={indirections}
33+
definitions={definitions}
34+
/>
35+
);
3036
}
3137

32-
function Schema({schema, indirections}) {
38+
function Schema({schema, indirections, definitions}) {
39+
const defs = definitions ?? schema.$defs;
40+
if (defs == null) {
41+
throw new Error(
42+
'Expected schema to have $defs or have non-standard definitions explicitly passed in.',
43+
);
44+
}
3345
return (
3446
<div className="json-schema">
3547
<SchemaDefinition
3648
definition={schema}
3749
name={schema.title}
3850
indirections={indirections}
3951
/>
40-
{Object.entries(schema.definitions).map(([name, definition], index) => {
41-
return (
42-
<SchemaDefinition
43-
key={index}
44-
definition={definition}
45-
name={name}
46-
indirections={indirections}
47-
/>
48-
);
49-
})}
52+
{defs != null &&
53+
Object.entries(defs).map(([name, definition], index) => {
54+
return (
55+
<SchemaDefinition
56+
key={index}
57+
definition={definition}
58+
name={name}
59+
indirections={indirections}
60+
/>
61+
);
62+
})}
5063
</div>
5164
);
5265
}
5366

5467
function SchemaDefinition({definition, name, indirections}) {
55-
if (indirections[name]) {
68+
if (indirections.getInlineDef(definition)) {
69+
// If this type is rendered inline, we don't need to render its definition.
5670
return null;
5771
}
5872
switch (definition.type) {
@@ -273,11 +287,12 @@ function T({prop, indirections}) {
273287
return String(prop);
274288
}
275289
if (prop.$ref) {
276-
const typeName = prop.$ref.split('/').pop();
277-
if (indirections[typeName]) {
278-
return <T prop={indirections[typeName]} indirections={indirections} />;
290+
const value = indirections.resolveRef(prop.$ref);
291+
const inlineDef = indirections.getInlineDef(value);
292+
if (inlineDef) {
293+
return <T prop={inlineDef} indirections={indirections} />;
279294
}
280-
295+
const typeName = prop.$ref.split('/').pop();
281296
if (typeName != null) {
282297
return <a href={'#' + typeName}>{typeName}</a>;
283298
}
@@ -417,45 +432,54 @@ function splitPascalCase(str) {
417432
// or shapes. First we collect up these types and build a map redirecting them to
418433
// their real type.
419434
function useIndirections(data) {
420-
return useMemo(() => {
421-
const indirections = {};
435+
return useMemo(() => new IndirectionResolver(data), [data]);
436+
}
422437

423-
Object.entries(data.definitions).forEach(([key, value]) => {
424-
// allOf is generally used to define a simple wrapper type
425-
if (value.allOf) {
426-
if (value.allOf.length !== 1) {
427-
throw new Error(
428-
'Expected allOf to only be used as a wrapper type wrapping a single type.',
429-
);
430-
}
431-
indirections[key] = value.allOf[0];
432-
}
438+
class IndirectionResolver {
439+
constructor(schema) {
440+
this._schema = schema;
441+
}
433442

434-
// $ref is used to define a type that is defined elsewhere
435-
if (value.$ref) {
436-
const typeName = value.$ref.split('/').pop();
437-
if (typeName != null) {
438-
indirections[key] = data.definitions[typeName];
439-
} else {
440-
throw new Error('Expected $ref to be a valid type name');
441-
}
443+
/**
444+
* Some types are trivial wrappers which are not worth showing as their own
445+
* types. Here we encode a heuristic for types which we think are not worth
446+
* showing as their own types, and instead we can use either the type they
447+
* wrap, or the structure of the type directly.
448+
*/
449+
getInlineDef(value) {
450+
if (value.type === 'string' || value.type === 'array') {
451+
return value;
452+
}
453+
if (Array.isArray(value.type)) {
454+
return value.type;
455+
}
456+
if (value.allOf) {
457+
if (value.allOf.length !== 1) {
458+
throw new Error(
459+
'Expected allOf to only be used as a wrapper type wrapping a single type.',
460+
);
442461
}
462+
return value.allOf[0];
463+
}
464+
if (value.$ref) {
465+
return this.getInlineDef(this.resolveRef(value.$ref));
466+
}
467+
return null;
468+
}
443469

444-
// Just use inline types
445-
if (value.type === 'array') {
446-
indirections[key] = value;
447-
}
448-
if (value.type === 'string') {
449-
indirections[key] = value;
450-
}
451-
if (Array.isArray(value.type)) {
452-
indirections[key] = value.type;
470+
resolveRef(ref) {
471+
const path = ref.split('/');
472+
let node = null;
473+
for (const segment of path) {
474+
if (segment === '#') {
475+
node = this._schema;
476+
} else {
477+
node = node[segment];
453478
}
454-
if (key === 'DeserializableProjectSet') {
455-
indirections[key] = value;
479+
if (!node) {
480+
throw new Error(`Reference ${ref} not found in schema.`);
456481
}
457-
});
458-
459-
return indirections;
460-
}, [data]);
482+
}
483+
return node;
484+
}
461485
}

website/versioned_docs/version-v20.0.0/getting-started/compiler-config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ keywords:
1010
hide_table_of_contents: false
1111
---
1212
import CompilerConfig from '@site/src/compiler-config/CompilerConfig';
13-
import schema from '@compilerConfigJsonSchema';
13+
import schema from './relay-compiler-config-schema.json';
1414

1515
## Compiler Config Options
1616

@@ -22,4 +22,4 @@ If you need more advanced options of the Relay Compiler Config, the exhaustive f
2222
Install the [Relay VSCode extension](../editor-support.md) to get autocomplete, hover tips, and type checking for the options in your Relay config.
2323
:::
2424

25-
<CompilerConfig schema={schema} />
25+
<CompilerConfig schema={schema} definitions={schema.definitions} />

0 commit comments

Comments
 (0)