Skip to content

Commit e60f8e1

Browse files
add reverse references
1 parent f44f922 commit e60f8e1

7 files changed

+259
-15
lines changed

src/Body.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function Body({ body }: Props) {
1414
const v = body as ValueBody;
1515
return (
1616
<>
17-
<ValueOf header="Value" value={v.value} />
17+
<ValueOf expanded={false} header="Body value" value={v.value} />
1818
{v.codegenName && <Details header="Code generation name" value={v.codegenName} />}
1919
</>
2020
);

src/CollapsingEndpoint.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useCallback } from 'react';
2+
import { useSchema } from './SchemaContext';
3+
import Details from './Details';
4+
import CollapsingDetails from './CollapsingDetails';
5+
import Endpoint from './Endpoint';
6+
7+
type Props = {
8+
header?: string;
9+
name: string;
10+
}
11+
12+
export default function CollapsingEndpoint({ header, name }: Props) {
13+
const schema = useSchema();
14+
15+
const renderEndpoint = useCallback(() => {
16+
for (const endpoint of schema.endpoints) {
17+
if (endpoint.name === name) {
18+
return <Endpoint endpoint={endpoint} />;
19+
}
20+
}
21+
return <Details value="No additional info" />
22+
}, [name, schema.endpoints]);
23+
24+
return (
25+
<CollapsingDetails header={header} value=<code>{ name }</code> cb={renderEndpoint} />
26+
);
27+
}

src/CollapsingType.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ export default function CollapsingType({ header, namespace, name }: Props) {
2626
}, [name, namespace, schema.types]);
2727

2828
return (
29-
<CollapsingDetails header={header} value=<code>{ `${namespace}::${name}` }</code> cb={renderType} />
29+
<CollapsingDetails header={header} value=<code>{`${namespace}::${name}` }</code> cb={renderType} />
3030
);
3131
}

src/Request.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function Request({ type }: Props) {
1616
return (
1717
<>
1818
{type.generics &&
19-
<CollapsingDetails expanded header="Generics">
19+
<CollapsingDetails header="Generics">
2020
{type.generics.map(g => (
2121
<CollapsingType key={`${g.namespace}::${g.name}`} namespace={g.namespace} name={g.name} />
2222
))}

src/SchemaContext.tsx

+184-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { createContext, useContext, useState, useEffect } from 'react';
22
import { compareVersions } from 'compare-versions';
33

4-
import { Model } from './metamodel';
4+
import { Model, Endpoint, TypeName, ValueOf } from './metamodel';
5+
6+
export const SchemaContext = createContext<Context | undefined>(undefined);
57

68
type Context = {
79
schema: Model | {};
10+
references: Record<string, Record<string, string[]>>;
811
version: string;
912
setVersion: (version: string) => void;
1013
allVersions: string[];
@@ -14,10 +17,181 @@ type Version = {
1417
name: string;
1518
}
1619

17-
export const SchemaContext = createContext<Context | undefined>(undefined);
20+
const crossReference = (schema: Model): Record<string, Record<string, string[]>> => {
21+
const refs: Record<string, Record<string, string[]>> = {};
22+
23+
const addEndpointReference = (source: Endpoint, type: TypeName, comment: string) => {
24+
const typeName = `${type.namespace}::${type.name}`;
25+
if (!(typeName in refs)) {
26+
refs[typeName] = {}
27+
}
28+
if (!('endpoints' in refs[typeName])) {
29+
refs[typeName].endpoints = [];
30+
}
31+
refs[typeName].endpoints.push(`${source.name} ${comment}`);
32+
}
33+
34+
const addTypeReference = (source: TypeName, type: TypeName, comment: string) => {
35+
const typeName = `${type.namespace}::${type.name}`;
36+
if (!(typeName in refs)) {
37+
refs[typeName] = {}
38+
}
39+
if (refs[typeName].types === undefined) {
40+
refs[typeName].types = [];
41+
}
42+
refs[typeName].types.push(`${source.namespace}::${source.name} ${comment}`);
43+
}
44+
45+
const addValueOfReferences = (source: TypeName, valueOf: ValueOf, comment: string) => {
46+
switch (valueOf.kind) {
47+
case 'instance_of':
48+
addTypeReference(source, valueOf.type, comment);
49+
break;
50+
case 'array_of':
51+
addValueOfReferences(source, valueOf.value, comment);
52+
break;
53+
case 'union_of':
54+
for (const item of valueOf.items) {
55+
addValueOfReferences(source, item, comment);
56+
}
57+
break;
58+
case 'dictionary_of':
59+
addValueOfReferences(source, valueOf.key, comment);
60+
addValueOfReferences(source, valueOf.value, comment);
61+
break;
62+
}
63+
}
64+
65+
// register types referenced in endpoints
66+
for (const e of schema.endpoints) {
67+
if (e.request) {
68+
addEndpointReference(e, e.request, 'Endpoint Request');
69+
}
70+
if (e.response) {
71+
addEndpointReference(e, e.response, 'Endpoint Response');
72+
}
73+
}
74+
75+
// register types references in other types
76+
for (const type of schema.types) {
77+
switch (type.kind) {
78+
case 'interface':
79+
if (type.generics) {
80+
for (const t of type.generics) {
81+
addTypeReference(type.name, t, 'Generic');
82+
}
83+
}
84+
if (type.inherits) {
85+
addTypeReference(type.name, type.inherits.type, 'Inherits');
86+
}
87+
if (type.behaviors) {
88+
for (const t of type.behaviors) {
89+
addTypeReference(type.name, t.type, 'Behavior');
90+
}
91+
}
92+
if (type.attachedBehaviors) {
93+
for (const b of type.attachedBehaviors) {
94+
addTypeReference(type.name, {namespace: '_spec_utils', name: b}, 'Attached behavior');
95+
}
96+
}
97+
for (const p of type.properties) {
98+
addValueOfReferences(type.name, p.type, `${p.name} (Property)`);
99+
}
100+
break;
101+
case 'request':
102+
if (type.generics) {
103+
for (const t of type.generics) {
104+
addTypeReference(type.name, t, 'Generic');
105+
}
106+
}
107+
if (type.inherits) {
108+
addTypeReference(type.name, type.inherits.type, 'Inherits');
109+
}
110+
if (type.behaviors) {
111+
for (const t of type.behaviors) {
112+
addTypeReference(type.name, t.type, 'Behavior');
113+
}
114+
}
115+
if (type.attachedBehaviors) {
116+
for (const b of type.attachedBehaviors) {
117+
addTypeReference(type.name, {namespace: '_spec_utils', name: b}, 'Attached behavior');
118+
}
119+
}
120+
for (const p of type.path) {
121+
addValueOfReferences(type.name, p.type, `${p.name} (path property)`);
122+
}
123+
for (const p of type.query) {
124+
addValueOfReferences(type.name, p.type, `${p.name} (Query property)`);
125+
}
126+
if (type.body.kind === "value") {
127+
addValueOfReferences(type.name, type.body.value, 'Body');
128+
}
129+
else if (type.body.kind === "properties") {
130+
for (const p of type.body.properties) {
131+
addValueOfReferences(type.name, p.type, `${p.name} (Body property)`);
132+
}
133+
}
134+
if (type.behaviors) {
135+
for (const t of type.behaviors) {
136+
addTypeReference(type.name, t.type, 'Behavior');
137+
}
138+
}
139+
break;
140+
case 'response':
141+
if (type.generics) {
142+
for (const t of type.generics) {
143+
addTypeReference(type.name, t, 'Generic');
144+
}
145+
}
146+
if (type.body.kind === "value") {
147+
addValueOfReferences(type.name, type.body.value, 'Body');
148+
}
149+
else if (type.body.kind === "properties") {
150+
for (const p of type.body.properties) {
151+
addValueOfReferences(type.name, p.type, `${p.name} (Body property)`);
152+
}
153+
}
154+
if (type.behaviors) {
155+
for (const t of type.behaviors) {
156+
addTypeReference(type.name, t.type, 'Behavior');
157+
}
158+
}
159+
if (type.attachedBehaviors) {
160+
for (const b of type.attachedBehaviors) {
161+
addTypeReference(type.name, {namespace: '_spec_utils', name: b}, 'Attached behavior');
162+
}
163+
}
164+
if (type.exceptions) {
165+
for (const e of type.exceptions) {
166+
if (e.body.kind === "value") {
167+
addValueOfReferences(type.name, e.body.value, 'Exception body');
168+
}
169+
else if (e.body.kind === "properties") {
170+
for (const p of e.body.properties) {
171+
addValueOfReferences(type.name, p.type, `${p.name} (Exception body property)`);
172+
}
173+
}
174+
}
175+
}
176+
break;
177+
case 'enum':
178+
break;
179+
case 'type_alias':
180+
addValueOfReferences(type.name, type.type, 'Type');
181+
if (type.generics) {
182+
for (const t of type.generics) {
183+
addTypeReference(type.name, t, 'Generic');
184+
}
185+
}
186+
break;
187+
}
188+
}
189+
return refs;
190+
};
18191

19192
export default function SchemaProvider({ children }: React.PropsWithChildren<{}>) {
20193
const [schema, setSchema] = useState<Model | {}>({});
194+
const [references, setReferences] = useState<Record<string, Record<string, string[]>>>({});
21195
const [version, setVersion] = useState<string>('');
22196
const [allVersions, setAllVersions] = useState<string[]>([]);
23197

@@ -41,13 +215,17 @@ export default function SchemaProvider({ children }: React.PropsWithChildren<{}>
41215
}
42216
(async () => {
43217
const r = await fetch(`https://raw.githubusercontent.com/elastic/elasticsearch-specification/refs/heads/${version}/output/schema/schema.json`);
44-
const s = await r.json();
45-
setSchema(s);
218+
if (r.status === 200) {
219+
const s = await r.json();
220+
const refs = crossReference(s);
221+
setSchema(s);
222+
setReferences(refs);
223+
}
46224
})();
47225
}, [version]);
48226

49227
return (
50-
<SchemaContext.Provider value={{ schema, version, setVersion, allVersions }}>
228+
<SchemaContext.Provider value={{ schema, references, version, setVersion, allVersions }}>
51229
{ children }
52230
</SchemaContext.Provider>
53231
);
@@ -61,5 +239,5 @@ export function useSchema(): Model {
61239

62240
export function useSchemaContext(): Context {
63241
const ctx = useContext(SchemaContext);
64-
return ctx ? ctx : { schema: {}, version: 'main', setVersion: (v) => {}, allVersions: [] };
242+
return ctx ? ctx : { schema: {}, references: {}, version: 'main', setVersion: (v) => {}, allVersions: [] };
65243
}

src/Type.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import Details from './Details';
22
import CollapsingDetails from './CollapsingDetails';
3+
import CollapsingEndpoint from './CollapsingEndpoint';
4+
import CollapsingType from './CollapsingType';
35
import Description from './Description';
46
import Deprecation from './Deprecation';
57
import Interface from './Interface';
68
import Request from './Request';
79
import Response from './Response';
810
import Enum from './Enum';
911
import TypeAlias from './TypeAlias';
12+
import { useSchemaContext } from './SchemaContext';
1013

1114
import { BaseType, Interface as InterfaceType, Request as RequestType, Response as ResponseType, Enum as EnumType, TypeAlias as TypeAliasType } from './metamodel';
1215

@@ -15,6 +18,19 @@ type Props = {
1518
}
1619

1720
export default function Type({ type }: Props) {
21+
const { references } = useSchemaContext();
22+
const typeName = `${type.name.namespace}::${type.name.name}`;
23+
let refCount = 0;
24+
25+
if (references[typeName] !== undefined) {
26+
if (references[typeName].endpoints) {
27+
refCount += references[typeName].endpoints.length;
28+
}
29+
if (references[typeName].types) {
30+
refCount += references[typeName].types.length;
31+
}
32+
}
33+
1834
return (
1935
<>
2036
<Details header="Kind" value={type.kind} />
@@ -50,6 +66,28 @@ export default function Type({ type }: Props) {
5066
<path fill-rule="evenodd" d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z"/>
5167
</svg>
5268
</a> />
69+
{typeName in references && (references[typeName].endpoints || references[typeName].types) &&
70+
<CollapsingDetails header="References" value={`${refCount}`}>
71+
{references[typeName].endpoints &&
72+
<>
73+
{references[typeName].endpoints.map(endpointName => {
74+
const comment = endpointName.split(' ').slice(1).join(' ');
75+
const name = endpointName.split(' ')[0];
76+
return <CollapsingEndpoint key={name} header={comment} name={name} />;
77+
})}
78+
</>
79+
}
80+
{references[typeName].types &&
81+
<>
82+
{references[typeName].types.map(typeName => {
83+
const comment = typeName.split(' ').slice(1).join(' ');
84+
const [namespace, name] = typeName.split(' ')[0].split('::')
85+
return <CollapsingType key={`${namespace}::${name}`} header={comment} namespace={namespace} name={name} />;
86+
})}
87+
</>
88+
}
89+
</CollapsingDetails>
90+
}
5391
</>
5492
);
5593
}

src/ValueOf.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import { InstanceOf, ArrayOf, UnionOf, DictionaryOf, LiteralValue } from './meta
88
type Props = {
99
header?: string;
1010
value: ValueOfType;
11+
expanded?: boolean
1112
}
1213

13-
export default function ValueOf({ header, value }: Props) {
14+
export default function ValueOf({ header, value, expanded }: Props) {
1415
if (value.kind === 'instance_of') {
1516
const v = value as InstanceOf;
1617
return (
17-
<CollapsingDetails expanded header={header ?? "Value"} value="Instance of">
18+
<CollapsingDetails expanded={expanded ?? true} header={header ?? "Value"} value="Instance of">
1819
<CollapsingType header="Type" namespace={v.type.namespace} name={v.type.name} />
1920
{v.generics &&
2021
<CollapsingDetails header="Generics">
@@ -29,15 +30,15 @@ export default function ValueOf({ header, value }: Props) {
2930
else if (value.kind === 'array_of') {
3031
const v = value as ArrayOf;
3132
return (
32-
<CollapsingDetails expanded header={header ?? "Value"} value="Array of">
33+
<CollapsingDetails expanded={expanded ?? true} header={header ?? "Value"} value="Array of">
3334
<ValueOf value={v.value} />
3435
</CollapsingDetails>
3536
);
3637
}
3738
else if (value.kind === 'union_of') {
3839
const v = value as UnionOf;
3940
return (
40-
<CollapsingDetails expanded header={header ?? "Value"} value="Union of">
41+
<CollapsingDetails expanded={expanded ?? true} header={header ?? "Value"} value="Union of">
4142
{v.items.map((t, i) => (
4243
<ValueOf key={i} value={t} />
4344
))}
@@ -47,7 +48,7 @@ export default function ValueOf({ header, value }: Props) {
4748
else if (value.kind === 'dictionary_of') {
4849
const v = value as DictionaryOf;
4950
return (
50-
<CollapsingDetails expanded header={header ?? "Value"} value="Dictionary of">
51+
<CollapsingDetails expanded={expanded ?? true} header={header ?? "Value"} value="Dictionary of">
5152
<ValueOf header="Keys" value={v.key} />
5253
<ValueOf header="Values" value={v.value} />
5354
<Details header="Single key" value={v.singleKey ? 'Yes' : 'No'} />
@@ -62,7 +63,7 @@ export default function ValueOf({ header, value }: Props) {
6263
else if (value.kind === 'literal_value') {
6364
const v = value as LiteralValue;
6465
return (
65-
<CollapsingDetails expanded header={header ?? "Value"} value="Literal value">
66+
<CollapsingDetails expanded={expanded ?? true} header={header ?? "Value"} value="Literal value">
6667
<Details header="Value" value={v.value.toString()} />
6768
</CollapsingDetails>
6869
);

0 commit comments

Comments
 (0)