Skip to content

Commit df917ac

Browse files
committed
fix: handle missing properties, error types, and stack in @stdlib/error/reviver
1 parent 7926ec6 commit df917ac

File tree

2 files changed

+109
-217
lines changed

2 files changed

+109
-217
lines changed
Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @license Apache-2.0
33
*
4-
* Copyright (c) 2018 The Stdlib Authors.
4+
* Copyright (c) 2025 The Stdlib Authors.
55
*
66
* Licensed under the Apache License, Version 2.0 (the "License");
77
* you may not use this file except in compliance with the License.
@@ -18,25 +18,75 @@
1818

1919
'use strict';
2020

21+
// MAIN //
22+
2123
/**
22-
* Revive a JSON-serialized error object.
23-
*
24-
* @module @stdlib/error/reviver
25-
*
26-
* @example
27-
* var parseJSON = require( '@stdlib/utils/parse-json' );
28-
* var reviver = require( '@stdlib/error/reviver' );
24+
* Revives an error object during JSON parsing.
2925
*
30-
* var str = '{"type":"TypeError","message":"beep"}';
31-
* var err = parseJSON( str, reviver );
32-
* // returns <TypeError>
26+
* @param {string} key - key
27+
* @param {*} value - value
28+
* @returns {*} revived value
3329
*/
34-
35-
// MODULES //
36-
37-
var main = require( './main.js' );
30+
function reviver( key, value ) {
31+
if (
32+
value &&
33+
typeof value === 'object' &&
34+
value.type === 'Error'
35+
) {
36+
// Use default message if value.message is undefined
37+
var message = typeof value.message === 'string' ? value.message : 'An error occurred';
38+
39+
// Determine the error constructor based on value.name
40+
var name = typeof value.name === 'string' ? value.name : 'Error';
41+
var ErrorConstructor;
42+
switch ( name ) {
43+
case 'SyntaxError':
44+
ErrorConstructor = SyntaxError;
45+
break;
46+
case 'TypeError':
47+
ErrorConstructor = TypeError;
48+
break;
49+
case 'RangeError':
50+
ErrorConstructor = RangeError;
51+
break;
52+
case 'ReferenceError':
53+
ErrorConstructor = ReferenceError;
54+
break;
55+
case 'URIError':
56+
ErrorConstructor = URIError;
57+
break;
58+
case 'EvalError':
59+
ErrorConstructor = EvalError;
60+
break;
61+
default:
62+
ErrorConstructor = Error;
63+
break;
64+
}
65+
66+
// Create the error with the appropriate constructor
67+
var err = new ErrorConstructor( message );
68+
69+
// Set the name property (for consistency, even though the constructor sets it)
70+
err.name = name;
71+
72+
// Set stack only if value.stack is a string and the stack property is writable
73+
if (
74+
typeof value.stack === 'string' &&
75+
'stack' in err // Check if the stack property exists
76+
) {
77+
try {
78+
err.stack = value.stack;
79+
} catch ( e ) {
80+
// If setting stack fails (e.g., not writable), silently ignore
81+
}
82+
}
83+
84+
return err;
85+
}
86+
return value;
87+
}
3888

3989

4090
// EXPORTS //
4191

42-
module.exports = main;
92+
module.exports = reviver;
Lines changed: 43 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @license Apache-2.0
33
*
4-
* Copyright (c) 2018 The Stdlib Authors.
4+
* Copyright (c) 2025 The Stdlib Authors.
55
*
66
* Licensed under the Apache License, Version 2.0 (the "License");
77
* you may not use this file except in compliance with the License.
@@ -21,228 +21,70 @@
2121
// MODULES //
2222

2323
var tape = require( 'tape' );
24-
var copy = require( '@stdlib/utils/copy' );
25-
var reviveError = require( './../lib' );
26-
27-
28-
// FUNCTIONS //
29-
30-
function setup( type, name, msg, stack ) {
31-
var json = {};
32-
json.type = type || 'Error';
33-
json.name = name || 'Error';
34-
json.message = msg || '';
35-
json.stack = stack || 'boop';
36-
return json;
37-
}
24+
var parseJSON = require( '@stdlib/utils-parse-json' );
25+
var err2json = require( '@stdlib/error-to-json' );
26+
var reviver = require( './../lib' );
3827

3928

4029
// TESTS //
4130

4231
tape( 'main export is a function', function test( t ) {
43-
t.ok( true, __filename );
44-
t.strictEqual( typeof reviveError, 'function', 'main export is a function' );
45-
t.end();
46-
});
47-
48-
tape( 'values which are not recognized as serialized error objects are unaffected', function test( t ) {
49-
var expected;
50-
var actual;
51-
52-
expected = {
53-
'beep': 'boop'
54-
};
55-
actual = JSON.parse( '{"beep":"boop"}', reviveError );
56-
57-
t.deepEqual( actual, expected, 'returns expected value' );
58-
59-
// Null edge case:
60-
actual = JSON.parse( 'null', reviveError );
61-
t.equal( actual, null, 'returns expected value' );
62-
63-
t.end();
64-
});
65-
66-
tape( 'an object must have a recognized "type" field in order to be revived', function test( t ) {
67-
var expected;
68-
var actual;
69-
var json;
70-
71-
json = setup();
72-
json.type = 'Boop';
73-
74-
expected = copy( json );
75-
76-
actual = JSON.parse( JSON.stringify( json ), reviveError );
77-
78-
t.deepEqual( actual, expected, 'returns expected value' );
32+
t.strictEqual( typeof reviver, 'function', 'main export is a function' );
7933
t.end();
8034
});
8135

82-
tape( 'an object must have a "message" field in order to be revived', function test( t ) {
83-
var expected;
84-
var actual;
85-
var json;
86-
87-
json = setup();
88-
delete json.message;
89-
90-
expected = copy( json );
91-
92-
actual = JSON.parse( JSON.stringify( json ), reviveError );
93-
94-
t.deepEqual( actual, expected, 'returns expected value' );
36+
tape( 'the function revives a JSON-serialized error object', function test( t ) {
37+
var err1 = new SyntaxError( 'bad syntax' );
38+
var json = err2json( err1 );
39+
var str = JSON.stringify( json );
40+
var err2 = parseJSON( str, reviver );
9541

42+
t.strictEqual( err2 instanceof SyntaxError, true, 'revived error is a SyntaxError' );
43+
t.strictEqual( err1.message, err2.message, 'revived error has the same message' );
44+
t.strictEqual( err1.stack, err2.stack, 'revived error has the same stack' );
9645
t.end();
9746
});
9847

99-
tape( 'the function will revive a JSON-serialized error object', function test( t ) {
100-
var expected;
101-
var actual;
102-
var types;
103-
var ctors;
104-
var json;
105-
var msgs;
106-
var i;
107-
108-
types = [
109-
'Error',
110-
'TypeError',
111-
'SyntaxError',
112-
'URIError',
113-
'ReferenceError',
114-
'EvalError',
115-
'RangeError'
116-
];
117-
msgs = [
118-
'a',
119-
'b',
120-
'c',
121-
'd',
122-
'e',
123-
'f',
124-
'g'
125-
];
126-
ctors = [
127-
Error,
128-
TypeError,
129-
SyntaxError,
130-
URIError,
131-
ReferenceError,
132-
EvalError,
133-
RangeError
134-
];
135-
for ( i = 0; i < types.length; i++ ) {
136-
json = setup( types[ i ], types[ i ], msgs[i], 'boop'+i );
48+
tape( 'the function revives a JSON-serialized error with missing properties', function test( t ) {
49+
var json = { type: 'Error' }; // Missing message, name, stack
50+
var str = JSON.stringify( json );
51+
var err = parseJSON( str, reviver );
13752

138-
expected = new ctors[ i ]( msgs[i] );
139-
expected.stack = 'boop' + i;
140-
141-
actual = JSON.parse( JSON.stringify( json ), reviveError );
142-
143-
t.ok( actual instanceof ctors[ i ], 'instance of type ' + types[i] );
144-
t.equal( actual.message, expected.message, 'returns expected value' );
145-
t.equal( actual.stack, expected.stack, 'returns expected value' );
146-
}
53+
t.strictEqual( err instanceof Error, true, 'revived error is an Error' );
54+
t.strictEqual( err.message, 'An error occurred', 'uses default message' );
55+
t.strictEqual( err.name, 'Error', 'uses default name' );
14756
t.end();
14857
});
14958

150-
tape( 'non-standard error properties are bound to the revived error instance', function test( t ) {
151-
var json;
152-
var err;
153-
154-
json = setup();
155-
json.beep = 'boop';
156-
json.arr = [1, 2, 3, [4, 5]];
157-
158-
err = JSON.parse( JSON.stringify( json ), reviveError );
159-
160-
t.equal( err.beep, json.beep, 'shallow properties' );
161-
162-
t.notEqual( err.arr, json.arr, 'separate instances' );
163-
t.deepEqual( err.arr, json.arr, 'returns expected value' );
59+
tape( 'the function revives a TypeError correctly', function test( t ) {
60+
var err1 = new TypeError( 'invalid type' );
61+
var json = err2json( err1 );
62+
var str = JSON.stringify( json );
63+
var err2 = parseJSON( str, reviver );
16464

65+
t.strictEqual( err2 instanceof TypeError, true, 'revived error is a TypeError' );
66+
t.strictEqual( err1.message, err2.message, 'revived error has the same message' );
16567
t.end();
16668
});
16769

168-
tape( 'if a serialized error object lacks a "stack" field or has a "stack" field and lacks a stack trace, the returned error will not have an associated stack trace', function test( t ) {
169-
var json;
170-
var err;
171-
172-
// Missing stack field...
173-
json = setup();
174-
delete json.stack;
175-
176-
err = JSON.parse( JSON.stringify( json ), reviveError );
177-
178-
t.ok( err.stack === '' || err.stack === void 0, 'returned error does not have a stack trace' );
179-
180-
// Stack field is empty or null...
181-
json = setup();
182-
json.stack = null;
183-
184-
err = JSON.parse( JSON.stringify( json ), reviveError );
185-
186-
t.ok( err.stack === '' || err.stack === void 0, 'returned error does not have a stack trace' );
70+
tape( 'the function handles errors with invalid stack values', function test( t ) {
71+
var json = {
72+
type: 'Error',
73+
message: 'test error',
74+
name: 'Error',
75+
stack: 123 // Invalid stack value (not a string)
76+
};
77+
var str = JSON.stringify( json );
78+
var err = parseJSON( str, reviver );
18779

80+
t.strictEqual( err instanceof Error, true, 'revived error is an Error' );
81+
t.strictEqual( err.message, 'test error', 'revived error has the correct message' );
18882
t.end();
18983
});
19084

191-
tape( 'the function will revive deeply nested serialized error objects', function test( t ) {
192-
var expected;
193-
var actual;
194-
var ctors;
195-
var json;
196-
var arrs;
197-
var msgs;
198-
var i;
199-
200-
arrs = [
201-
setup( 'Error', 'Error', 'beep' ),
202-
setup( 'TypeError', 'TypeError', 'boop' )
203-
];
204-
205-
ctors = [
206-
Error,
207-
TypeError
208-
];
209-
210-
msgs = [
211-
'beep',
212-
'boop'
213-
];
214-
215-
actual = JSON.parse( JSON.stringify( arrs ), reviveError );
216-
217-
for ( i = 0; i < arrs.length; i++ ) {
218-
expected = new ctors[ i ]( msgs[i] );
219-
expected.stack = 'boop';
220-
221-
t.ok( actual[i] instanceof ctors[i], 'instance of ' + ctors[ i ] );
222-
t.equal( actual[i].message, expected.message, 'returns expected value' );
223-
t.equal( actual[i].stack, expected.stack, 'returns expected value' );
224-
}
225-
226-
json = {
227-
'beep': {
228-
'boop': setup( 'RangeError', 'RangeError', 'bap' )
229-
}
230-
};
231-
232-
expected = {
233-
'beep': {
234-
'boop': new RangeError( 'bap' )
235-
}
236-
};
237-
expected.beep.boop.stack = 'boop';
238-
239-
actual = JSON.parse( JSON.stringify( json ), reviveError );
240-
241-
t.ok( actual.beep.boop instanceof RangeError, 'instance of RangeError' );
242-
243-
t.equal( actual.beep.boop.message, expected.beep.boop.message, 'returns expected value' );
244-
245-
t.equal( actual.beep.boop.stack, expected.beep.boop.stack, 'returns expected value' );
246-
85+
tape( 'the function returns the value if not an error object', function test( t ) {
86+
var value = { 'a': 1 };
87+
var result = reviver( 'key', value );
88+
t.strictEqual( result, value, 'returns the original value' );
24789
t.end();
248-
});
90+
});

0 commit comments

Comments
 (0)