diff --git a/.changeset/lovely-sheep-prove.md b/.changeset/lovely-sheep-prove.md new file mode 100644 index 0000000..7eab0c8 --- /dev/null +++ b/.changeset/lovely-sheep-prove.md @@ -0,0 +1,5 @@ +--- +"devalue": minor +--- + +feat: expose `DevalueError` for `instanceof` checks in `catch` clauses diff --git a/.changeset/odd-areas-enjoy.md b/.changeset/odd-areas-enjoy.md new file mode 100644 index 0000000..8034061 --- /dev/null +++ b/.changeset/odd-areas-enjoy.md @@ -0,0 +1,5 @@ +--- +"devalue": minor +--- + +feat: add `value` and `root` properties in `DevalueError` instances diff --git a/index.js b/index.js index 2bdd761..1e8fc6b 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ export { uneval } from './src/uneval.js'; export { parse, unflatten } from './src/parse.js'; export { stringify } from './src/stringify.js'; +export { DevalueError } from './src/utils.js'; diff --git a/src/stringify.js b/src/stringify.js index 602ac1d..041fc19 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -64,7 +64,7 @@ export function stringify(value, reducers) { } if (typeof thing === 'function') { - throw new DevalueError(`Cannot stringify a function`, keys); + throw new DevalueError(`Cannot stringify a function`, keys, thing, value); } let str = ''; @@ -200,14 +200,18 @@ export function stringify(value, reducers) { if (!is_plain_object(thing)) { throw new DevalueError( `Cannot stringify arbitrary non-POJOs`, - keys + keys, + thing, + value ); } if (enumerable_symbols(thing).length > 0) { throw new DevalueError( `Cannot stringify POJOs with symbolic keys`, - keys + keys, + thing, + value ); } diff --git a/src/uneval.js b/src/uneval.js index 6656bd4..cf4a5de 100644 --- a/src/uneval.js +++ b/src/uneval.js @@ -47,7 +47,7 @@ export function uneval(value, replacer) { } if (typeof thing === 'function') { - throw new DevalueError(`Cannot stringify a function`, keys); + throw new DevalueError(`Cannot stringify a function`, keys, thing, value); } const type = get_type(thing); @@ -116,14 +116,18 @@ export function uneval(value, replacer) { if (!is_plain_object(thing)) { throw new DevalueError( `Cannot stringify arbitrary non-POJOs`, - keys + keys, + thing, + value ); } if (enumerable_symbols(thing).length > 0) { throw new DevalueError( `Cannot stringify POJOs with symbolic keys`, - keys + keys, + thing, + value ); } diff --git a/src/utils.js b/src/utils.js index 2ad6055..04e67ee 100644 --- a/src/utils.js +++ b/src/utils.js @@ -15,11 +15,15 @@ export class DevalueError extends Error { /** * @param {string} message * @param {string[]} keys + * @param {any} [value] - The value that failed to be serialized + * @param {any} [root] - The root value being serialized */ - constructor(message, keys) { + constructor(message, keys, value, root) { super(message); this.name = 'DevalueError'; this.path = keys.join(''); + this.value = value; + this.root = root; } } diff --git a/test/test.js b/test/test.js index 07aa97a..bb4e7e3 100644 --- a/test/test.js +++ b/test/test.js @@ -867,6 +867,67 @@ for (const fn of [uneval, stringify]) { assert.equal(e.path, '.object.invalid'); } }); + + uvu.test(`${fn.name} populates error.value with the problematic value`, () => { + const testFn = function invalid() {}; + try { + fn({ + foo: { + array: [testFn] + } + }); + } catch (e) { + assert.equal(e.name, 'DevalueError'); + assert.equal(e.message, 'Cannot stringify a function'); + assert.equal(e.value, testFn); + } + }); + + uvu.test(`${fn.name} populates error.root with the root value`, () => { + const root = { + foo: { + array: [function invalid() {}] + } + }; + try { + fn(root); + } catch (e) { + assert.equal(e.name, 'DevalueError'); + assert.equal(e.message, 'Cannot stringify a function'); + assert.equal(e.root, root); + } + }); + + uvu.test(`${fn.name} includes value and root on arbitrary non-POJOs error`, () => { + class Whatever {} + const problematicValue = new Whatever(); + const root = { + foo: { + ['string-key']: new Map([['key', problematicValue]]) + } + }; + try { + fn(root); + } catch (e) { + assert.equal(e.name, 'DevalueError'); + assert.equal(e.message, 'Cannot stringify arbitrary non-POJOs'); + assert.equal(e.value, problematicValue); + assert.equal(e.root, root); + } + }); + + uvu.test(`${fn.name} includes value and root on symbolic keys error`, () => { + const symbolKey = Symbol('key'); + const root = { [symbolKey]: 'value' }; + try { + fn(root); + } catch (e) { + assert.equal(e.name, 'DevalueError'); + assert.equal(e.message, 'Cannot stringify POJOs with symbolic keys'); + assert.equal(e.value, root); + assert.equal(e.root, root); + } + }); } uvu.test('does not create duplicate parameter names', () => {