Skip to content

Commit 0940036

Browse files
gnidantest
authored andcommitted
web: document the identification field and list its schema
1 parent cf94e56 commit 0940036

8 files changed

Lines changed: 176 additions & 6 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
sidebar_position: 4
3+
---
4+
5+
import SchemaViewer from "@site/src/components/SchemaViewer";
6+
7+
# Identification
8+
9+
:::tip[Summary]
10+
11+
**ethdebug/format/data/identification** names the schema an object
12+
conforms to and the version of the specification that defines that
13+
schema. An object carries it in an `ethdebug` field.
14+
15+
:::
16+
17+
Each of the three root schemas, **ethdebug/format/program**,
18+
**ethdebug/format/info** and **ethdebug/format/info/resources**,
19+
includes **ethdebug/format/data/identification** under the key
20+
`ethdebug`. An object in any of these schemas may carry a field
21+
shaped like this:
22+
23+
```json
24+
{
25+
"ethdebug": {
26+
"schema": "ethdebug/format/program",
27+
"version": "<the current version>"
28+
}
29+
}
30+
```
31+
32+
`schema` is the name of the schema the object conforms to; each root
33+
schema pins this to its own name. The schema's `$id` is `schema:`
34+
followed by this name. `version` is the version of the specification
35+
that defines `schema`.
36+
37+
<SchemaViewer schema={{ id: "schema:ethdebug/format/data/identification" }} />
38+
39+
## Rules
40+
41+
### Absent field
42+
43+
An object without this field predates it.
44+
45+
### Nesting
46+
47+
A program nested inside an info document (at `programs[i]`) may omit
48+
the field. If both the info document and the program carry it, their
49+
`version` values **must** be equal.
50+
51+
The same rule applies to a program that sits beside a resources object.
52+
53+
For example, in solc's standard JSON output, `evm.bytecode.ethdebug`
54+
and `evm.deployedBytecode.ethdebug` are programs that sit beside the
55+
top-level `ethdebug` wrapper, whose `resources` member is the resources
56+
object.
57+
58+
### Required later
59+
60+
The field is optional for now. It becomes required in the stable
61+
`0.1.0` release.

‎packages/web/spec/data/overview.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ bar for complete contents.
3535
- [Non-negative numeric values](/spec/data/value)
3636
- [Hexadecimal strings](/spec/data/hex)
3737
- [Unsigned integer](/spec/data/unsigned)
38+
- [Identification](/spec/data/identification)

‎packages/web/spec/info/info.mdx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ import SchemaViewer from "@site/src/components/SchemaViewer";
99
# Schema
1010

1111
<SchemaViewer schema={{ id: "schema:ethdebug/format/info" }} />
12+
13+
## Identification
14+
15+
This object may carry an `ethdebug` field; see
16+
[Identification](/spec/data/identification).

‎packages/web/spec/info/resources.mdx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ import SchemaViewer from "@site/src/components/SchemaViewer";
77
# Resources lookup schema
88

99
<SchemaViewer schema={{ id: "schema:ethdebug/format/info/resources" }} />
10+
11+
## Identification
12+
13+
This object may carry an `ethdebug` field; see
14+
[Identification](/spec/data/identification).
15+
16+
An info document names **ethdebug/format/info** in this field instead.

‎packages/web/spec/overview.mdx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ For the full collection of raw schema listings (in YAML format), please see the
7171
[`schemas/` directory](https://github.com/ethdebug/format/tree/main/schemas)
7272
in this project's GitHub repository.
7373

74+
A `schema:ethdebug/format/<path>` id names the schema whose source is
75+
`schemas/<path>.schema.yaml` in that repository. The id is an
76+
identifier, not a web address. Emitted data **should not** place it in a
77+
`$schema` key. See [Identification](/spec/data/identification) for how
78+
objects name the schema they conform to.
79+
7480
## Conventions used by this format
7581

7682
### Terminology

‎packages/web/spec/program/program.mdx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ import SchemaViewer from "@site/src/components/SchemaViewer";
99
# Schema
1010

1111
<SchemaViewer schema={{ id: "schema:ethdebug/format/program" }} />
12+
13+
## Identification
14+
15+
This object may carry an `ethdebug` field; see
16+
[Identification](/spec/data/identification).

‎packages/web/src/components/SchemaViewer.tsx‎

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
2020
const rootSchemaInfo = describeSchema(props);
2121
const { id, rootSchema, yaml: _yaml, pointer } = rootSchemaInfo;
2222

23-
const transformedSchema = transformSchema(rootSchema, id || "");
23+
// the schema this page shows is outermost in the dynamic scope, so its
24+
// dynamic anchors take precedence over those of any schema it references
25+
const pageAnchors = dynamicAnchors(rootSchema);
26+
27+
const transformedSchema = transformSchema(rootSchema, id || "", pageAnchors);
2428

2529
return (
2630
<Tabs>
@@ -39,10 +43,13 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
3943
schema: {
4044
resolve: (uri: URL) => {
4145
const id = uri.toString();
42-
const { schema } = describeSchema({
46+
const { schema, rootSchema } = describeSchema({
4347
schema: { id },
4448
});
45-
return transformSchema(schema, id);
49+
return transformSchema(schema, id, {
50+
...dynamicAnchors(rootSchema),
51+
...pageAnchors,
52+
});
4653
},
4754
},
4855
},
@@ -97,8 +104,81 @@ export default function SchemaViewer(props: SchemaViewerProps): JSX.Element {
97104
);
98105
}
99106

100-
function transformSchema(schema: JSONSchema, id: string): JSONSchema {
101-
return insertIds(ensureRefsLackSiblings(schema), `${id}#`);
107+
type DynamicAnchors = { [name: string]: JSONSchema };
108+
109+
function transformSchema(
110+
schema: JSONSchema,
111+
id: string,
112+
anchors: DynamicAnchors,
113+
): JSONSchema {
114+
return insertIds(
115+
ensureRefsLackSiblings(resolveDynamicRefs(schema, anchors)),
116+
`${id}#`,
117+
);
118+
}
119+
120+
// collects the schemas in a root schema's `$defs` that declare a
121+
// `$dynamicAnchor`, keyed by anchor name
122+
function dynamicAnchors(rootSchema: JSONSchema): DynamicAnchors {
123+
const anchors: DynamicAnchors = {};
124+
125+
const definitions =
126+
(typeof rootSchema === "object" && rootSchema.$defs) || {};
127+
128+
for (const definition of Object.values(definitions)) {
129+
if (typeof definition !== "object") {
130+
continue;
131+
}
132+
133+
const { $dynamicAnchor, ...rest } = definition as {
134+
$dynamicAnchor?: string;
135+
};
136+
137+
if (typeof $dynamicAnchor === "string") {
138+
anchors[$dynamicAnchor] = rest as JSONSchema;
139+
}
140+
}
141+
142+
return anchors;
143+
}
144+
145+
// recursively replaces each `{ $dynamicRef: "#name" }` with the schema
146+
// that the dynamic anchor `name` resolves to.
147+
//
148+
// docusaurus-json-schema-plugin reports any `$dynamicRef` as an unresolved
149+
// reference, so this integration resolves them itself. The caller supplies
150+
// the anchors in dynamic-scope order: those of the page's own schema
151+
// override those of a schema that the page references.
152+
function resolveDynamicRefs<T>(obj: T, anchors: DynamicAnchors): T {
153+
if (!obj || typeof obj !== "object") {
154+
return obj;
155+
}
156+
157+
if (Array.isArray(obj)) {
158+
return obj.map((item) => resolveDynamicRefs(item, anchors)) as T;
159+
}
160+
161+
const { $dynamicRef, ...rest } = obj as T & object & { $dynamicRef?: string };
162+
163+
const result = Object.entries(rest).reduce((newObj, [key, value]) => {
164+
// @ts-expect-error dynamic key assignment
165+
newObj[key] = resolveDynamicRefs(value, anchors);
166+
return newObj;
167+
}, {} as T);
168+
169+
if ($dynamicRef === undefined) {
170+
return result;
171+
}
172+
173+
const anchor = $dynamicRef.startsWith("#")
174+
? anchors[$dynamicRef.slice(1)]
175+
: undefined;
176+
177+
if (!anchor) {
178+
throw new Error(`Could not resolve $dynamicRef "${$dynamicRef}"`);
179+
}
180+
181+
return { ...anchor, ...result };
102182
}
103183

104184
function insertIds<T>(obj: T, rootId: string): T {

‎packages/web/src/schemas.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ const pointerSchemaIndex: SchemaIndex = {
175175
.reduce((a, b) => ({ ...a, ...b }), {}),
176176
};
177177

178-
const dataSchemaIndex: SchemaIndex = ["value", "hex", "unsigned"]
178+
const dataSchemaIndex: SchemaIndex = [
179+
"value",
180+
"hex",
181+
"unsigned",
182+
"identification",
183+
]
179184
.map((name) => ({
180185
[`schema:ethdebug/format/data/${name}`]: {
181186
href: `/spec/data/${name}`,

0 commit comments

Comments
 (0)