diff --git a/lib/pointer.ts b/lib/pointer.ts index c86ac67c..b1b2c965 100644 --- a/lib/pointer.ts +++ b/lib/pointer.ts @@ -88,6 +88,7 @@ class Pointer = Parser */ resolve(obj: S, options?: O, pathFromRoot?: string) { const tokens = Pointer.parse(this.path, this.originalPath); + const found: any = []; // Crawl the object, one token at a time this.value = unwrapOrThrow(obj); @@ -103,6 +104,7 @@ class Pointer = Parser } const token = tokens[i]; + if (this.value[token] === undefined || (this.value[token] === null && i === tokens.length - 1)) { // one final case is if the entry itself includes slashes, and was parsed out as a token - we can join the remaining tokens and try again let didFindSubstringSlashMatch = false; @@ -120,10 +122,23 @@ class Pointer = Parser } this.value = null; - throw new MissingPointerError(token, decodeURI(this.originalPath)); + + let path: any = ''; + + if (path !== undefined) { + path = this.$ref.path; + } + + const targetRef = this.path.replace(path, ''); + const targetFound = Pointer.join('', found); + const parentPath = pathFromRoot?.replace(path, ''); + + throw new MissingPointerError(token, decodeURI(this.originalPath), targetRef, targetFound, parentPath); } else { this.value = this.value[token]; } + + found.push(token) } // Resolve the final value diff --git a/lib/util/errors.ts b/lib/util/errors.ts index 256c8a1c..3a643bd8 100644 --- a/lib/util/errors.ts +++ b/lib/util/errors.ts @@ -123,8 +123,17 @@ export class UnmatchedResolverError extends JSONParserError { export class MissingPointerError extends JSONParserError { code = "EMISSINGPOINTER" as JSONParserErrorType; name = "MissingPointerError"; - constructor(token: string, path: string) { + public targetToken: any; + public targetRef: string; + public targetFound: string; + public parentPath: string; + constructor(token: any, path: any, targetRef: any, targetFound: any, parentPath: any) { super(`Missing $ref pointer "${getHash(path)}". Token "${token}" does not exist.`, stripHash(path)); + + this.targetToken = token; + this.targetRef = targetRef; + this.targetFound = targetFound; + this.parentPath = parentPath; } } diff --git a/test/specs/missing-pointers/error-details.yaml b/test/specs/missing-pointers/error-details.yaml new file mode 100644 index 00000000..d2a02adb --- /dev/null +++ b/test/specs/missing-pointers/error-details.yaml @@ -0,0 +1,16 @@ +paths: + /pet: + post: + tags: + - pet + parameters: + - $ref: '#/components/parameters/ThisIsMissing' +components: + parameters: + petId: + name: petId + in: path + required: true + schema: + type: integer + format: int64 \ No newline at end of file diff --git a/test/specs/missing-pointers/missing-pointers.spec.ts b/test/specs/missing-pointers/missing-pointers.spec.ts index f3bc5156..b0cf5e61 100644 --- a/test/specs/missing-pointers/missing-pointers.spec.ts +++ b/test/specs/missing-pointers/missing-pointers.spec.ts @@ -90,5 +90,28 @@ describe("Schema with missing pointers", () => { ]); } }); + + it("should throw an missing pointer error with details for target and parent", async () => { + const parser = new $RefParser(); + try { + await parser.dereference({ foo: { $ref: path.abs("test/specs/missing-pointers/error-details.yaml") }}, { continueOnError: true }); + helper.shouldNotGetCalled(); + } + catch (err) { + expect(err).to.be.instanceof(JSONParserErrorGroup); + expect(err.files).to.equal(parser); + expect(err.message).to.have.string("1 error occurred while reading '"); + expect(err.errors).to.containSubset([ + { + name: MissingPointerError.name, + message: 'Missing $ref pointer \"#/components/parameters/ThisIsMissing\". Token \"ThisIsMissing\" does not exist.', + targetToken: 'ThisIsMissing', + targetRef: "#/components/parameters/ThisIsMissing", + targetFound: "#/components/parameters", + parentPath: "#/paths/~1pet/post/parameters/0" + } + ]); + } + }); }); });