Skip to content

Commit 90e8abd

Browse files
committed
change: rename schemaId » schemaLocation, spointer » evaluationPath
1 parent 6f0b0d8 commit 90e8abd

37 files changed

+217
-178
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Changelog
22

3+
- reference for error-format: https://json-schema.org/blog/posts/fixing-json-schema-output
4+
35
### v9.0.0
46

57
- [Breaking] error data to always contain `schema` and `value`

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ const titleData = titleNode?.getData();
112112

113113
```ts
114114
const titleNode = compileSchema(mySchema).getNode("#/image/title");
115-
console.log(titleNode.spointer); // #/properties/image/properties/title
116-
console.log(titleNode.schemaId); // #/properties/image/properties/title
115+
console.log(titleNode.evaluationPath); // #/properties/image/properties/title
116+
console.log(titleNode.schemaLocation); // #/properties/image/properties/title
117117
```
118118

119-
- `spointer` refers to the path in schema and is extended by `$ref`, e.g. if image is defined on `$defs`: `#/properties/image/$ref/properties/title`
120-
- `schemaId` refers to the absolute path within the schema and will not change, e.g. `#/$defs/properties/title`
119+
- `evaluationPath` refers to the path in schema and is extended by `$ref`, e.g. if image is defined on `$defs`: `#/properties/image/$ref/properties/title`
120+
- `schemaLocation` refers to the absolute path within the schema and will not change, e.g. `#/$defs/properties/title`
121121

122122
</details>
123123

@@ -1097,9 +1097,9 @@ export const notKeyword: Keyword = {
10971097
};
10981098

10991099
export function parseNot(node: SchemaNode) {
1100-
const { schema, spointer, schemaId } = node;
1100+
const { schema, evaluationPath, schemaLocation } = node;
11011101
if (schema.not != null) {
1102-
node.not = node.compileSchema(schema.not, `${spointer}/not`, `${schemaId}/not`);
1102+
node.not = node.compileSchema(schema.not, `${evaluationPath}/not`, `${schemaLocation}/not`);
11031103
}
11041104
}
11051105
```
@@ -1147,7 +1147,7 @@ export const typeKeyword: Keyword = {
11471147
function reduceType({ node, pointer, data }: JsonSchemaReducerParams): undefined | SchemaNode {
11481148
const dataType = getJsonSchemaType(data, node.schema.type);
11491149
if (dataType !== "undefined" && Array.isArray(node.schema.type) && node.schema.type.includes(dataType)) {
1150-
return node.compileSchema({ ...node.schema, pointer, type: dataType }, node.spointer);
1150+
return node.compileSchema({ ...node.schema, pointer, type: dataType }, node.evaluationPath);
11511151
}
11521152
return undefined;
11531153
}

src/SchemaNode.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,25 @@ export interface SchemaNode extends SchemaNodeMethodsType {
7777
context: Context;
7878
/** JSON Schema of node */
7979
schema: JsonSchema;
80-
/** absolute path into JSON Schema, includes $ref for resolved schema */
81-
spointer: string;
82-
/** local path within JSON Schema (not extended by resolving ref) */
83-
schemaId: string;
80+
/**
81+
* Evaluation Path - The location of the keyword that produced the annotation or error.
82+
* The purpose of this data is to show the resolution path which resulted in the subschema
83+
* that contains the keyword.
84+
*
85+
* - relative to the root of the principal schema; should include (inline) any $ref segments in the path
86+
* - JSON pointer
87+
*/
88+
evaluationPath: string;
89+
/**
90+
* Schema Location - The direct location to the keyword that produced the annotation
91+
* or error. This is provided as a convenience to the user so that they don't have to resolve
92+
* the keyword's subschema, which may not be trivial task. It is only provided if the relative
93+
* location contains $refs (otherwise, the two locations will be the same).
94+
*
95+
* - absolute URI
96+
* - may not have any association to the principal schema
97+
*/
98+
schemaLocation: string;
8499
/** id created when combining subschemas */
85100
dynamicId: string;
86101
/** reference to parent node (node used to compile this node) */
@@ -163,19 +178,19 @@ export const SchemaNodeMethods = {
163178
*/
164179
compileSchema(
165180
schema: JsonSchema,
166-
spointer: string = this.spointer,
167-
schemaId?: string,
181+
evaluationPath: string = this.evaluationPath,
182+
schemaLocation?: string,
168183
dynamicId?: string
169184
): SchemaNode {
170-
const nextFragment = spointer.split("/$ref")[0];
185+
const nextFragment = evaluationPath.split("/$ref")[0];
171186
const parentNode = this as SchemaNode;
172187
const node: SchemaNode = {
173188
lastIdPointer: parentNode.lastIdPointer, // ref helper
174189
context: parentNode.context,
175190
parent: parentNode,
176-
spointer,
191+
evaluationPath,
177192
dynamicId: joinDynamicId(parentNode.dynamicId, dynamicId),
178-
schemaId: schemaId ?? join(parentNode.schemaId, nextFragment),
193+
schemaLocation: schemaLocation ?? join(parentNode.schemaLocation, nextFragment),
179194
reducers: [],
180195
resolvers: [],
181196
validators: [],
@@ -258,15 +273,15 @@ export const SchemaNodeMethods = {
258273
return { node, error: undefined };
259274
// @ts-expect-error bool schema
260275
} else if (node.schema === true) {
261-
const nextNode = node.compileSchema(createSchema(data), node.spointer, node.schemaId);
276+
const nextNode = node.compileSchema(createSchema(data), node.evaluationPath, node.schemaLocation);
262277
path?.push({ pointer, node });
263278
return { node: nextNode, error: undefined };
264279
}
265280

266281
let schema;
267282
// we need to copy node to prevent modification of source
268283
// @todo does mergeNode break immutability?
269-
let workingNode = node.compileSchema(node.schema, node.spointer, node.schemaId);
284+
let workingNode = node.compileSchema(node.schema, node.evaluationPath, node.schemaLocation);
270285
const reducers = node.reducers;
271286
for (let i = 0; i < reducers.length; i += 1) {
272287
const result = reducers[i]({ data, key, node, pointer, path });
@@ -339,9 +354,9 @@ export const SchemaNodeMethods = {
339354
const draft = getDraft(context.drafts, schema?.$schema ?? this.context.rootNode.$schema);
340355

341356
const node: SchemaNode = {
342-
spointer: "#",
357+
evaluationPath: "#",
343358
lastIdPointer: "#",
344-
schemaId: "#",
359+
schemaLocation: "#",
345360
dynamicId: "",
346361
reducers: [],
347362
resolvers: [],
@@ -379,7 +394,7 @@ export const SchemaNodeMethods = {
379394
},
380395

381396
toJSON() {
382-
return { ...this, context: undefined, errors: undefined, parent: this.parent?.spointer };
397+
return { ...this, context: undefined, errors: undefined, parent: this.parent?.evaluationPath };
383398
}
384399
} as const;
385400

src/compileSchema.reduceSchema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ describe("compileSchema : reduceNode", () => {
821821
assert.deepEqual(node.dynamicId, "#(dependencies/one,dependencies/two)");
822822
});
823823

824-
it("should prefix with schemaId", () => {
824+
it("should prefix with schemaLocation", () => {
825825
const { node } =
826826
compileSchema({
827827
properties: {

src/compileSchema.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -141,41 +141,41 @@ describe("compileSchema getDataDefaultOptions", () => {
141141
});
142142
});
143143

144-
describe("compileSchema `schemaId`", () => {
145-
it("should store path from rootSchema as schemaId", () => {
144+
describe("compileSchema `schemaLocation`", () => {
145+
it("should store path from rootSchema as schemaLocation", () => {
146146
const node = compileSchema({
147147
if: { type: "string" },
148148
then: { type: "string" },
149149
properties: { title: { type: "string" } },
150150
$defs: { asset: { type: "string" } }
151151
});
152152

153-
assert.deepEqual(node.schemaId, "#");
154-
assert.deepEqual(node.if.schemaId, "#/if");
155-
assert.deepEqual(node.then.schemaId, "#/then");
156-
assert.deepEqual(node.properties.title.schemaId, "#/properties/title");
157-
assert.deepEqual(node.$defs.asset.schemaId, "#/$defs/asset");
153+
assert.deepEqual(node.schemaLocation, "#");
154+
assert.deepEqual(node.if.schemaLocation, "#/if");
155+
assert.deepEqual(node.then.schemaLocation, "#/then");
156+
assert.deepEqual(node.properties.title.schemaLocation, "#/properties/title");
157+
assert.deepEqual(node.$defs.asset.schemaLocation, "#/$defs/asset");
158158
});
159159

160-
it("should maintain schemaId when resolved by ref", () => {
160+
it("should maintain schemaLocation when resolved by ref", () => {
161161
const { node } = compileSchema({
162162
properties: { title: { $ref: "#/$defs/asset" } },
163163
$defs: { asset: { type: "string" } }
164164
}).getNodeChild("title");
165165

166166
// @todo should have returned already resolved node?
167167
const result = node.resolveRef();
168-
assert.deepEqual(result.schemaId, "#/$defs/asset");
168+
assert.deepEqual(result.schemaLocation, "#/$defs/asset");
169169
});
170170

171-
it("should maintain schemaId when resolved by root-ref", () => {
171+
it("should maintain schemaLocation when resolved by root-ref", () => {
172172
const { node } = compileSchema({
173173
properties: { title: { $ref: "#" } }
174174
}).getNodeChild("title");
175175

176176
// @todo should have returned already resolved node?
177177
const result = node.resolveRef();
178-
assert.deepEqual(result.schemaId, "#");
178+
assert.deepEqual(result.schemaLocation, "#");
179179
});
180180
});
181181

src/compileSchema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ export function compileSchema(schema: JsonSchema, options: CompileOptions = {})
3535
const draft = getDraft(drafts, schema?.$schema);
3636

3737
const node: SchemaNode = {
38-
spointer: "#",
38+
evaluationPath: "#",
3939
lastIdPointer: "#",
40-
schemaId: "#",
40+
schemaLocation: "#",
4141
dynamicId: "",
4242
reducers: [],
4343
resolvers: [],

src/draft04/keywords/$ref.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function parseRef(node: SchemaNode) {
2525
let currentId = node.parent?.$id;
2626
if (node.schema?.$ref == null && node.schema?.id) {
2727
currentId = joinId(node.parent?.$id, node.schema.id);
28-
// console.log("create id", node.spointer, ":", node.parent?.$id, node.schema?.id, "=>", currentId);
28+
// console.log("create id", node.evaluationPath, ":", node.parent?.$id, node.schema?.id, "=>", currentId);
2929
}
3030
node.$id = currentId;
3131
node.lastIdPointer = node.parent?.lastIdPointer ?? "#";
@@ -40,17 +40,17 @@ function parseRef(node: SchemaNode) {
4040

4141
const idChanged = currentId !== node.parent?.$id;
4242
if (idChanged) {
43-
node.lastIdPointer = node.spointer;
43+
node.lastIdPointer = node.evaluationPath;
4444
}
4545

4646
// store this node for retrieval by id + json-pointer from id
47-
if (node.lastIdPointer !== "#" && node.spointer.startsWith(node.lastIdPointer)) {
48-
const localPointer = `#${node.spointer.replace(node.lastIdPointer, "")}`;
47+
if (node.lastIdPointer !== "#" && node.evaluationPath.startsWith(node.lastIdPointer)) {
48+
const localPointer = `#${node.evaluationPath.replace(node.lastIdPointer, "")}`;
4949
register(node, joinId(currentId, localPointer));
5050
} else {
51-
register(node, joinId(currentId, node.spointer));
51+
register(node, joinId(currentId, node.evaluationPath));
5252
}
53-
register(node, joinId(node.context.rootNode.$id, node.spointer));
53+
register(node, joinId(node.context.rootNode.$id, node.evaluationPath));
5454

5555
// precompile reference
5656
if (node.schema.$ref) {
@@ -76,27 +76,27 @@ function resolveRef({ pointer, path }: { pointer?: string; path?: ValidationPath
7676
return resolvedNode;
7777
}
7878

79-
function compileNext(referencedNode: SchemaNode, spointer = referencedNode.spointer) {
79+
function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode.evaluationPath) {
8080
const referencedSchema = isObject(referencedNode.schema)
8181
? omit(referencedNode.schema, "id")
8282
: referencedNode.schema;
83-
return referencedNode.compileSchema(referencedSchema, `${spointer}/$ref`, referencedSchema.schemaId);
83+
return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedSchema.schemaLocation);
8484
}
8585

8686
function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
8787
if ($ref == null) {
8888
return node;
8989
}
9090

91-
// resolve $ref by json-spointer
91+
// resolve $ref by json-evaluationPath
9292
if (node.context.refs[$ref]) {
9393
// console.log(`ref resolve ${$ref} from refs`, node.context.refs[$ref].ref);
94-
return compileNext(node.context.refs[$ref], node.spointer);
94+
return compileNext(node.context.refs[$ref], node.evaluationPath);
9595
}
9696

9797
if (node.context.anchors[$ref]) {
9898
// console.log(`ref resolve ${$ref} from anchors`, node.context.anchors[$ref].ref);
99-
return compileNext(node.context.anchors[$ref], node.spointer);
99+
return compileNext(node.context.anchors[$ref], node.evaluationPath);
100100
}
101101

102102
// check for remote-host + pointer pair to switch rootSchema
@@ -111,7 +111,7 @@ function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
111111
const $ref = fragments[0];
112112
// this is a reference to remote-host root node
113113
if (node.context.remotes[$ref]) {
114-
return compileNext(node.context.remotes[$ref], node.spointer);
114+
return compileNext(node.context.remotes[$ref], node.evaluationPath);
115115
}
116116
// console.error("REF: UNFOUND 1", $ref, Object.keys(node.context.remotes));
117117
return undefined;

src/draft06/keywords/$ref.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ function parseRef(node: SchemaNode) {
3131

3232
const idChanged = currentId !== node.parent?.$id;
3333
if (idChanged) {
34-
node.lastIdPointer = node.spointer;
34+
node.lastIdPointer = node.evaluationPath;
3535
}
3636

3737
// store this node for retrieval by $id + json-pointer from $id
38-
if (node.lastIdPointer !== "#" && node.spointer.startsWith(node.lastIdPointer)) {
39-
const localPointer = `#${node.spointer.replace(node.lastIdPointer, "")}`;
38+
if (node.lastIdPointer !== "#" && node.evaluationPath.startsWith(node.lastIdPointer)) {
39+
const localPointer = `#${node.evaluationPath.replace(node.lastIdPointer, "")}`;
4040
node.context.refs[joinId(currentId, localPointer)] = node;
4141
} else {
42-
node.context.refs[joinId(currentId, node.spointer)] = node;
42+
node.context.refs[joinId(currentId, node.evaluationPath)] = node;
4343
}
44-
node.context.refs[joinId(node.context.rootNode.$id, node.spointer)] = node;
44+
node.context.refs[joinId(node.context.rootNode.$id, node.evaluationPath)] = node;
4545

4646
// precompile reference
4747
if (node.schema.$ref) {

src/draft2019-09/keywords/$ref.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ export function parseRef(node: SchemaNode) {
3232
const currentId = joinId(node.parent?.$id, node.schema?.$id);
3333
node.$id = currentId;
3434
node.lastIdPointer = node.parent?.lastIdPointer ?? "#";
35-
if (currentId !== node.parent?.$id && node.spointer !== "#") {
36-
node.lastIdPointer = node.spointer;
35+
if (currentId !== node.parent?.$id && node.evaluationPath !== "#") {
36+
node.lastIdPointer = node.evaluationPath;
3737
}
3838

3939
// store this node for retrieval by $id + json-pointer from $id
40-
if (node.lastIdPointer !== "#" && node.spointer.startsWith(node.lastIdPointer)) {
41-
const localPointer = `#${node.spointer.replace(node.lastIdPointer, "")}`;
40+
if (node.lastIdPointer !== "#" && node.evaluationPath.startsWith(node.lastIdPointer)) {
41+
const localPointer = `#${node.evaluationPath.replace(node.lastIdPointer, "")}`;
4242
register(node, joinId(currentId, localPointer));
4343
}
4444
// store $rootId + json-pointer to this node
45-
register(node, joinId(node.context.rootNode.$id, node.spointer));
45+
register(node, joinId(node.context.rootNode.$id, node.evaluationPath));
4646
// store this node for retrieval by $id + anchor
4747
if (node.schema.$anchor) {
4848
node.context.anchors[`${currentId.replace(/#$/, "")}#${node.schema.$anchor}`] = node;
@@ -59,7 +59,7 @@ export function parseRef(node: SchemaNode) {
5959

6060
// export function reduceRef({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
6161
// const resolvedNode = node.resolveRef({ pointer, path });
62-
// if (resolvedNode.schemaId === node.schemaId) {
62+
// if (resolvedNode.schemaLocation === node.schemaLocation) {
6363
// return resolvedNode;
6464
// }
6565
// const result = resolvedNode.reduceNode(data, { key, pointer, path });
@@ -128,26 +128,26 @@ function resolveRecursiveRef(node: SchemaNode, path: ValidationPath): SchemaNode
128128
return nextNode;
129129
}
130130

131-
function compileNext(referencedNode: SchemaNode, spointer = referencedNode.spointer) {
131+
function compileNext(referencedNode: SchemaNode, evaluationPath = referencedNode.evaluationPath) {
132132
const referencedSchema = isObject(referencedNode.schema)
133133
? omit(referencedNode.schema, "$id")
134134
: referencedNode.schema;
135135

136-
return referencedNode.compileSchema(referencedSchema, `${spointer}/$ref`, referencedNode.schemaId);
136+
return referencedNode.compileSchema(referencedSchema, `${evaluationPath}/$ref`, referencedNode.schemaLocation);
137137
}
138138

139139
export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode | undefined {
140140
if ($ref == null) {
141141
return node;
142142
}
143143

144-
// resolve $ref by json-spointer
144+
// resolve $ref by json-evaluationPath
145145
if (node.context.refs[$ref]) {
146-
return compileNext(node.context.refs[$ref], node.spointer);
146+
return compileNext(node.context.refs[$ref], node.evaluationPath);
147147
}
148148
// resolve $ref from $anchor
149149
if (node.context.anchors[$ref]) {
150-
return compileNext(node.context.anchors[$ref], node.spointer);
150+
return compileNext(node.context.anchors[$ref], node.evaluationPath);
151151
}
152152

153153
// check for remote-host + pointer pair to switch rootSchema
@@ -162,7 +162,7 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
162162
const $ref = fragments[0];
163163
// this is a reference to remote-host root node
164164
if (node.context.remotes[$ref]) {
165-
return compileNext(node.context.remotes[$ref], node.spointer);
165+
return compileNext(node.context.remotes[$ref], node.evaluationPath);
166166
}
167167
if ($ref[0] === "#") {
168168
// @todo there is a bug joining multiple fragments to e.g. #/base#/examples/0
@@ -172,7 +172,7 @@ export default function getRef(node: SchemaNode, $ref = node?.$ref): SchemaNode
172172
const rootSchema = node.context.rootNode.schema;
173173
const targetSchema = get(rootSchema, ref);
174174
if (targetSchema) {
175-
return node.compileSchema(targetSchema, `${node.spointer}/$ref`, ref);
175+
return node.compileSchema(targetSchema, `${node.evaluationPath}/$ref`, ref);
176176
}
177177
}
178178
// console.error("REF: UNFOUND 1", $ref);

0 commit comments

Comments
 (0)