Skip to content

Commit da0fda2

Browse files
authored
fix: allow $ref pointers to point to a null value (#374)
* fix: allow `$ref` pointers to point to a `null` value * fix: removing a `.only()` test designation * fix: pr feedback
1 parent 5303da9 commit da0fda2

File tree

5 files changed

+58
-2
lines changed

5 files changed

+58
-2
lines changed

lib/pointer.ts

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import * as url from "./util/url.js";
55
import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
66
import type { JSONSchema } from "./types";
77

8+
export const nullSymbol = Symbol('null');
9+
810
const slashes = /\//g;
911
const tildes = /~/g;
1012
const escapedSlash = /~1/g;
@@ -121,6 +123,16 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
121123
continue;
122124
}
123125

126+
// If the token we're looking for ended up not containing any slashes but is
127+
// actually instead pointing to an existing `null` value then we should use that
128+
// `null` value.
129+
if (token in this.value && this.value[token] === null) {
130+
// We use a `null` symbol for internal tracking to differntiate between a general `null`
131+
// value and our expected `null` value.
132+
this.value = nullSymbol;
133+
continue;
134+
}
135+
124136
this.value = null;
125137

126138
const path = this.$ref.path || "";

lib/ref.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Pointer from "./pointer.js";
1+
import Pointer, { nullSymbol } from "./pointer.js";
22
import type { JSONParserError, MissingPointerError, ParserError, ResolverError } from "./util/errors.js";
33
import { InvalidPointerError, isHandledError, normalizeError } from "./util/errors.js";
44
import { safePointerToPath, stripHash, getHash } from "./util/url.js";
@@ -119,7 +119,12 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
119119
resolve(path: string, options?: O, friendlyPath?: string, pathFromRoot?: string) {
120120
const pointer = new Pointer<S, O>(this, path, friendlyPath);
121121
try {
122-
return pointer.resolve(this.value, options, pathFromRoot);
122+
const resolved = pointer.resolve(this.value, options, pathFromRoot);
123+
if (resolved.value === nullSymbol) {
124+
resolved.value = null;
125+
}
126+
127+
return resolved;
123128
} catch (err: any) {
124129
if (!options || !options.continueOnError || !isHandledError(err)) {
125130
throw err;
@@ -148,6 +153,9 @@ class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOpt
148153
set(path: string, value: any) {
149154
const pointer = new Pointer(this, path);
150155
this.value = pointer.set(this.value, value);
156+
if (this.value === nullSymbol) {
157+
this.value = null;
158+
}
151159
}
152160

153161
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { describe, it } from "vitest";
2+
import { expect } from "vitest";
3+
import $RefParser from "../../../lib/index.js";
4+
import path from "../../utils/path";
5+
import dereferenced from "./dereferenced.js";
6+
7+
describe("dereferencing a `$ref` that points to a `null` value", () => {
8+
it("should dereference successfully", async () => {
9+
const parser = new $RefParser();
10+
const schema = await parser.dereference(path.rel("test/specs/dereference-null-ref/dereference-null-ref.yaml"));
11+
expect(schema).to.equal(parser.schema);
12+
expect(schema).to.deep.equal(dereferenced);
13+
});
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
components:
2+
examples:
3+
product:
4+
value:
5+
data:
6+
admission:
7+
$ref: "#/components/examples/product/value/data/pas"
8+
pas: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default {
2+
components: {
3+
examples: {
4+
product: {
5+
value: {
6+
data: {
7+
admission: null,
8+
pas: null,
9+
},
10+
},
11+
},
12+
},
13+
},
14+
};

0 commit comments

Comments
 (0)