diff --git a/src/compile.js b/src/compile.js index 00c451d..41074e5 100644 --- a/src/compile.js +++ b/src/compile.js @@ -2,6 +2,10 @@ import escape from './escape.js'; import JSONPointerCompileError from './errors/JSONPointerCompileError.js'; const compile = (referenceTokens) => { + if (!Array.isArray(referenceTokens)) { + throw new TypeError('Reference tokens must be a list of strings or numbers'); + } + try { if (referenceTokens.length === 0) { return ''; @@ -16,8 +20,9 @@ const compile = (referenceTokens) => { }) .join('/')}`; } catch (error) { - throw new JSONPointerCompileError('Unknown error during JSON Pointer compilation', { + throw new JSONPointerCompileError('Unexpected error during JSON Pointer compilation', { cause: error, + referenceTokens, }); } }; diff --git a/src/errors/JSONPointerKeyError.js b/src/errors/JSONPointerKeyError.js index 7065358..be5a407 100644 --- a/src/errors/JSONPointerKeyError.js +++ b/src/errors/JSONPointerKeyError.js @@ -1,8 +1,15 @@ import JSONPointerEvaluateError from './JSONPointerEvaluateError.js'; class JSONPointerKeyError extends JSONPointerEvaluateError { - constructor(referenceToken, options) { - super(`Invalid object key: '${referenceToken}' not found`, options); + constructor(message, options) { + if ( + typeof message === 'undefined' && + (typeof options?.referenceToken === 'string' || typeof options?.referenceToken === 'number') + ) { + message = `Invalid object key: '${options.referenceToken}' not found`; + } + + super(message, options); } } diff --git a/src/errors/JSONPointerTypeError.js b/src/errors/JSONPointerTypeError.js index ce7bd3b..a48c418 100644 --- a/src/errors/JSONPointerTypeError.js +++ b/src/errors/JSONPointerTypeError.js @@ -1,11 +1,15 @@ import JSONPointerEvaluateError from './JSONPointerEvaluateError.js'; class JSONPointerTypeError extends JSONPointerEvaluateError { - constructor(referenceToken, options) { - super( - `Reference token '${referenceToken}' cannot be applied to non object/array value)`, - options, - ); + constructor(message, options) { + if ( + typeof message === 'undefined' && + (typeof options?.referenceToken === 'string' || typeof options?.referenceToken === 'number') + ) { + message = `Reference token '${options.referenceToken}' cannot be applied to non object/array value)`; + } + + super(message, options); } } diff --git a/src/evaluate.js b/src/evaluate.js index c9f4c08..2e75df2 100644 --- a/src/evaluate.js +++ b/src/evaluate.js @@ -15,19 +15,23 @@ const evaluate = ( const { result, computed: referenceTokens } = parse(jsonPointer, parseOptions); if (!result.success) { - throw new JSONPointerEvaluateError(`Invalid JSON Pointer: ${jsonPointer}`); + throw new JSONPointerEvaluateError(`Invalid JSON Pointer: ${jsonPointer}`, { + jsonPointer, + }); } - return referenceTokens.reduce((current, referenceToken) => { - if (typeof current !== 'object' || current === null) { - throw new JSONPointerTypeError(referenceToken); - } - + return referenceTokens.reduce((current, referenceToken, referenceTokenPosition) => { if (Array.isArray(current)) { if (testArrayDash(referenceToken)) { if (strictArrays) { throw new JSONPointerIndexError( 'Invalid array index: "-" always refers to a nonexistent element during evaluation', + { + jsonPointer, + referenceTokens, + referenceToken, + referenceTokenPosition, + }, ); } else { return current[current.length]; @@ -37,21 +41,47 @@ const evaluate = ( if (!testArrayIndex(referenceToken) && strictArrays) { throw new JSONPointerIndexError( `Invalid array index: '${referenceToken}' (MUST be "0", or digits without a leading "0")`, + { + jsonPointer, + referenceTokens, + referenceToken, + referenceTokenPosition, + }, ); } const index = Number(referenceToken); if (index >= current.length && strictArrays) { - throw new JSONPointerIndexError(`Invalid array index: '${index}' out of bounds`); + throw new JSONPointerIndexError(`Invalid array index: '${index}' out of bounds`, { + jsonPointer, + referenceTokens, + referenceToken: index, + referenceTokenPosition, + }); } return current[index]; } - if (!Object.prototype.hasOwnProperty.call(current, referenceToken) && strictObjects) { - throw new JSONPointerKeyError(referenceToken); + if (typeof current === 'object' && current !== null) { + if (!Object.prototype.hasOwnProperty.call(current, referenceToken) && strictObjects) { + throw new JSONPointerKeyError(undefined, { + jsonPointer, + referenceTokens, + referenceToken, + referenceTokenPosition, + }); + } + + return current[referenceToken]; } - return current[referenceToken]; + throw new JSONPointerTypeError(undefined, { + jsonPointer, + referenceTokens, + referenceToken, + referenceTokenPosition, + currentValue: current, + }); }, value); }; diff --git a/src/parse/index.js b/src/parse/index.js index 66e49c6..f1c185a 100644 --- a/src/parse/index.js +++ b/src/parse/index.js @@ -10,7 +10,7 @@ const grammar = new Grammar(); const parse = (jsonPointer, { evaluator = referenceTokenListEvaluator } = {}) => { if (typeof jsonPointer !== 'string') { - throw new JSONPointerParseError('JSON Pointer must be a string'); + throw new TypeError('JSON Pointer must be a string'); } try { @@ -31,7 +31,10 @@ const parse = (jsonPointer, { evaluator = referenceTokenListEvaluator } = {}) => return { result, ast, computed }; } catch (error) { - throw new JSONPointerParseError('Unknown error during JSON Pointer parsing', { cause: error }); + throw new JSONPointerParseError('Unexpected error during JSON Pointer parsing', { + cause: error, + jsonPointer, + }); } }; diff --git a/src/unescape.js b/src/unescape.js index 019f1af..0d35930 100644 --- a/src/unescape.js +++ b/src/unescape.js @@ -1,4 +1,8 @@ const unescape = (referenceToken) => { + if (typeof referenceToken !== 'string') { + throw new TypeError('Reference token must be a string'); + } + return referenceToken.replace(/~1/g, '/').replace(/~0/g, '~'); }; diff --git a/test/compile.js b/test/compile.js index a51d97a..6309d25 100644 --- a/test/compile.js +++ b/test/compile.js @@ -40,6 +40,6 @@ describe('compile', function () { }); it('should throw error on invalid input', function () { - assert.throws(() => compile(null), JSONPointerCompileError); + assert.throws(() => compile(null), TypeError); }); }); diff --git a/test/parse.js b/test/parse.js index a081eb7..866819d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -1,6 +1,6 @@ import { assert } from 'chai'; -import { parse, JSONPointerParseError } from '../src/index.js'; +import { parse } from '../src/index.js'; describe('parse', function () { context('given valid source string', function () { @@ -229,10 +229,10 @@ describe('parse', function () { context('given non-string input', function () { specify('should throw error', function () { - assert.throws(() => parse([]), JSONPointerParseError); - assert.throws(() => parse(1), JSONPointerParseError); - assert.throws(() => parse(null), JSONPointerParseError); - assert.throws(() => parse(undefined), JSONPointerParseError); + assert.throws(() => parse([]), TypeError); + assert.throws(() => parse(1), TypeError); + assert.throws(() => parse(null), TypeError); + assert.throws(() => parse(undefined), TypeError); }); }); });