├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test ├── common.js ├── from-object-test.js ├── mocha.opts ├── parse-test.js ├── register-all-test.js ├── register-object-test.js ├── register-test.js ├── set-defaults-test.js ├── stringify-test.js └── to-object-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - iojs 6 | script: npm run coverage && npm run coveralls 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Errio 2 | 3 | [![npm](https://img.shields.io/npm/v/errio.svg?style=flat-square)](https://www.npmjs.com/package/errio) 4 | [![Travis](https://img.shields.io/travis/programble/errio.svg?style=flat-square)](https://travis-ci.org/programble/errio) 5 | [![Coveralls](https://img.shields.io/coveralls/programble/errio.svg?style=flat-square)](https://coveralls.io/r/programble/errio) 6 | 7 | Error serialization and deserialization. 8 | 9 | ``` 10 | npm install errio 11 | ``` 12 | 13 | ## Overview 14 | 15 | JavaScript errors don't serialize well. 16 | 17 | ```javascript 18 | > JSON.stringify(new Error('help')); 19 | '{}' 20 | ``` 21 | 22 | And they certainly don't deserialize well. 23 | 24 | ```javascript 25 | > JSON.parse('{}'); 26 | {} 27 | ``` 28 | 29 | Errio serializes errors to meaningful JSON. 30 | 31 | ```javascript 32 | > Errio.stringify(new Error('serialize me')); 33 | '{"name":"Error","message":"serialize me"}' 34 | ``` 35 | 36 | And deserializes JSON back into error instances. 37 | 38 | ```javascript 39 | > Errio.parse('{"name":"Error","message":"serialize me"}'); 40 | [Error: serialize me] 41 | ``` 42 | 43 | ## Example 44 | 45 | Consult the [API Documentation][docs] for details on the functions used. 46 | 47 | This example uses [SuperError][super-error], a library for easily 48 | subclassing errors in Node.js. 49 | 50 | ```javascript 51 | var Errio = require('errio'); 52 | var SuperError = require('super-error'); 53 | var fs = require('fs'); 54 | 55 | // Create a new Error subclass with a custom constructor. 56 | var MyError = SuperError.subclass('MyError', function(code, message) { 57 | this.code = code; 58 | this.message = message; 59 | }); 60 | 61 | // Register the class with Errio. 62 | Errio.register(MyError); 63 | 64 | // Create an error instance. 65 | var error = new MyError(420, 'Enhance Your Calm'); 66 | 67 | // Save the error somewhere. 68 | fs.writeFileSync('error.json', Errio.stringify(error)); 69 | 70 | // Load the error from somewhere. 71 | var loadedError = Errio.parse(fs.readFileSync('error.json')); 72 | 73 | // Throw as usual. 74 | try { 75 | throw loadedError; 76 | } catch (thrown) { 77 | // Check class as usual. 78 | if (thrown instanceof MyError) { 79 | console.log(thrown.code); // And access properties as usual. 80 | } 81 | } 82 | ``` 83 | 84 | [super-error]: https://github.com/busbud/super-error 85 | [docs]: #api-documentation 86 | 87 | ## API Documentation 88 | 89 | ```javascript 90 | var Errio = require('errio'); 91 | ``` 92 | 93 | ### Options 94 | 95 | Options can be set at a global defaults level, at the error class level 96 | and at the individual call level. Listed below are the available options 97 | and their factory default values. 98 | 99 | - `recursive: true`: Recursively serialize and deserialize nested errors 100 | - `inherited: true`: Include inherited properties 101 | - `stack: false`: Include the stack trace 102 | - `private: false`: Include properties with leading or trailing 103 | underscores in serialization 104 | - `exclude: []`: Property names to exclude (low priority) 105 | - `include: []`: Property names to include (high priority) 106 | 107 | ### Errio.setDefaults(options) 108 | 109 | Overwrite the global default options with the plain object `options`. 110 | Option keys missing from `options` are left unchanged. 111 | 112 | ### Errio.register(constructor, options) 113 | 114 | Register an error constructor for serialization and deserialization with 115 | option overrides. 116 | 117 | The error name will be taken from the first of these that is set: 118 | 119 | 1. `options.name` 120 | 2. `constructor.prototype.name`, if it is not 'Error' 121 | 3. `constructor.name` 122 | 4. `new constructor().name` 123 | 124 | Note that in the last case, the constructor is instantiated with no arguments. 125 | 126 | All [built-in error classes][builtins] are automatically registered with 127 | no option overrides. 128 | 129 | Can be called more than once for the same error constructor in order to 130 | replace the option overrides. 131 | 132 | [builtins]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types 133 | 134 | ### Errio.registerAll(constructors, options) 135 | 136 | Register an array of error constructors with the same option overrides. 137 | 138 | Names cannot be specified in `options`, so all constructors will be 139 | instantiated to infer their names. 140 | 141 | ### Errio.registerObject(constructors, options) 142 | 143 | Register a plain object of error names mapped to constructors with the 144 | same option overrides. Constructors will not be instantiated. 145 | 146 | Perfect for registering error classes exported from a module. 147 | 148 | ### Errio.toObject(error, options) 149 | 150 | Serialize an error instance to a plain object with option overrides. 151 | 152 | Passed options take priority over registered error class options and the 153 | global defaults. 154 | 155 | If the class of the error instance has not been registered, it is 156 | automatically registered with the options passed to the call. 157 | 158 | Returned objects always contain `name` and `message` properties. 159 | 160 | ### Errio.fromObject(object, options) 161 | 162 | Deserialize a plain object to an instance of a registered error class. 163 | 164 | If the class of the serialized error has no registered constructor, 165 | return an instance of `Error` with the `name` property set. 166 | 167 | If the stack was not serialized, capture a new stack trace from the 168 | caller. 169 | 170 | ### Errio.stringify(error, options) 171 | 172 | Serialize an error instance to a JSON string. Convenience wrapper for 173 | `Errio.toObject`. 174 | 175 | ### Errio.parse(string, options) 176 | 177 | Deserialize a JSON string to an instance of a registered error class. 178 | Convenience wrapper for `Errio.fromObject`. 179 | 180 | ## License 181 | 182 | Copyright © 2015, Curtis McEnroe 183 | 184 | Permission to use, copy, modify, and/or distribute this software for any 185 | purpose with or without fee is hereby granted, provided that the above 186 | copyright notice and this permission notice appear in all copies. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 189 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 190 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 191 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 192 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 193 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 194 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 195 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Default options for all serializations. 4 | var defaultOptions = { 5 | recursive: true, // Recursively serialize and deserialize nested errors 6 | inherited: true, // Include inherited properties 7 | stack: false, // Include stack property 8 | private: false, // Include properties with leading or trailing underscores 9 | exclude: [], // Property names to exclude (low priority) 10 | include: [] // Property names to include (high priority) 11 | }; 12 | 13 | // Overwrite global default options. 14 | exports.setDefaults = function(options) { 15 | for (var key in options) defaultOptions[key] = options[key]; 16 | }; 17 | 18 | // Object containing registered error constructors and their options. 19 | var errors = {}; 20 | 21 | // Register an error constructor for serialization and deserialization with 22 | // option overrides. Name can be specified in options, otherwise it will be 23 | // taken from the prototype's name property (if it is not set to Error), the 24 | // constructor's name property, or the name property of an instance of the 25 | // constructor. 26 | exports.register = function(constructor, options) { 27 | options = options || {}; 28 | var prototypeName = constructor.prototype.name !== 'Error' 29 | ? constructor.prototype.name 30 | : null; 31 | var name = options.name 32 | || prototypeName 33 | || constructor.name 34 | || new constructor().name; 35 | errors[name] = { constructor: constructor, options: options }; 36 | }; 37 | 38 | // Register an array of error constructors all with the same option overrides. 39 | exports.registerAll = function(constructors, options) { 40 | constructors.forEach(function(constructor) { 41 | exports.register(constructor, options); 42 | }); 43 | }; 44 | 45 | // Shallow clone a plain object. 46 | function cloneObject(object) { 47 | var clone = {}; 48 | for (var key in object) { 49 | if (object.hasOwnProperty(key)) clone[key] = object[key]; 50 | } 51 | return clone; 52 | } 53 | 54 | // Register a plain object of constructor names mapped to constructors with 55 | // common option overrides. 56 | exports.registerObject = function(constructors, commonOptions) { 57 | for (var name in constructors) { 58 | if (!constructors.hasOwnProperty(name)) continue; 59 | 60 | var constructor = constructors[name]; 61 | var options = cloneObject(commonOptions); 62 | options.name = name; 63 | 64 | exports.register(constructor, options); 65 | } 66 | }; 67 | 68 | // Register the built-in error constructors. 69 | exports.registerAll([ 70 | Error, 71 | EvalError, 72 | RangeError, 73 | ReferenceError, 74 | SyntaxError, 75 | TypeError, 76 | URIError 77 | ]); 78 | 79 | // Serialize an error instance to a plain object with option overrides, applied 80 | // on top of the global defaults and the registered option overrides. If the 81 | // constructor of the error instance has not been registered yet, register it 82 | // with the provided options. 83 | exports.toObject = function(error, callOptions) { 84 | callOptions = callOptions || {}; 85 | 86 | if (!errors[error.name]) { 87 | // Make sure we register with the name of this instance. 88 | callOptions.name = error.name; 89 | exports.register(error.constructor, callOptions); 90 | } 91 | 92 | var errorOptions = errors[error.name].options; 93 | var options = {}; 94 | for (var key in defaultOptions) { 95 | if (callOptions.hasOwnProperty(key)) options[key] = callOptions[key]; 96 | else if (errorOptions.hasOwnProperty(key)) options[key] = errorOptions[key]; 97 | else options[key] = defaultOptions[key]; 98 | } 99 | 100 | // Always explicitly include essential error properties. 101 | var object = { 102 | name: error.name, 103 | message: error.message 104 | }; 105 | // Explicitly include stack since it is not always an enumerable property. 106 | if (options.stack) object.stack = error.stack; 107 | 108 | for (var prop in error) { 109 | // Skip exclusion checks if property is in include list. 110 | if (options.include.indexOf(prop) === -1) { 111 | if (typeof error[prop] === 'function') continue; 112 | 113 | if (options.exclude.indexOf(prop) !== -1) continue; 114 | 115 | if (!options.inherited) 116 | if (!error.hasOwnProperty(prop)) continue; 117 | if (!options.stack) 118 | if (prop === 'stack') continue; 119 | if (!options.private) 120 | if (prop[0] === '_' || prop[prop.length - 1] === '_') continue; 121 | } 122 | 123 | var value = error[prop]; 124 | 125 | // Recurse if nested object has name and message properties. 126 | if (typeof value === 'object' && value && value.name && value.message) { 127 | if (options.recursive) { 128 | object[prop] = exports.toObject(value, callOptions); 129 | } 130 | continue; 131 | } 132 | 133 | object[prop] = value; 134 | } 135 | 136 | return object; 137 | }; 138 | 139 | // Deserialize a plain object to an instance of a registered error constructor 140 | // with option overrides. If the specific constructor is not registered, 141 | // return a generic Error instance. If stack was not serialized, capture a new 142 | // stack trace. 143 | exports.fromObject = function(object, callOptions) { 144 | callOptions = callOptions || {}; 145 | 146 | var registration = errors[object.name]; 147 | if (!registration) registration = errors.Error; 148 | 149 | var constructor = registration.constructor; 150 | var errorOptions = registration.options; 151 | 152 | var options = {}; 153 | for (var key in defaultOptions) { 154 | if (callOptions.hasOwnProperty(key)) options[key] = callOptions[key]; 155 | else if (errorOptions.hasOwnProperty(key)) options[key] = errorOptions[key]; 156 | else options[key] = defaultOptions[key]; 157 | } 158 | 159 | // Instantiate the error without actually calling the constructor. 160 | var error = Object.create(constructor.prototype); 161 | 162 | for (var prop in object) { 163 | // Recurse if nested object has name and message properties. 164 | if (options.recursive && typeof object[prop] === 'object') { 165 | var nested = object[prop]; 166 | if (nested && nested.name && nested.message) { 167 | error[prop] = exports.fromObject(nested, callOptions); 168 | continue; 169 | } 170 | } 171 | 172 | error[prop] = object[prop]; 173 | } 174 | 175 | // Capture a new stack trace such that the first trace line is the caller of 176 | // fromObject. 177 | if (!error.stack && Error.captureStackTrace) { 178 | Error.captureStackTrace(error, exports.fromObject); 179 | } 180 | 181 | return error; 182 | }; 183 | 184 | // Serialize an error instance to a JSON string with option overrides. 185 | exports.stringify = function(error, callOptions) { 186 | return JSON.stringify(exports.toObject(error, callOptions)); 187 | }; 188 | 189 | // Deserialize a JSON string to an instance of a registered error constructor. 190 | exports.parse = function(string, callOptions) { 191 | return exports.fromObject(JSON.parse(string), callOptions); 192 | }; 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "errio", 3 | "version": "1.2.2", 4 | "description": "Error serialization and deserialization", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "NODE_ENV=test node_modules/.bin/mocha", 8 | "coverage": "NODE_ENV=test node_modules/.bin/istanbul cover node_modules/.bin/_mocha", 9 | "coveralls": "cat coverage/lcov.info | node_modules/.bin/coveralls", 10 | "clean": "rm -rf coverage" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/programble/errio" 15 | }, 16 | "keywords": [ 17 | "error", 18 | "serialization", 19 | "deserialization", 20 | "json" 21 | ], 22 | "author": "Curtis McEnroe (http://cmcenroe.me)", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/programble/errio/issues" 26 | }, 27 | "homepage": "https://github.com/programble/errio", 28 | "devDependencies": { 29 | "coveralls": "^2.11.2", 30 | "istanbul": "^0.3.5", 31 | "mocha": "^2.1.0", 32 | "super-error": "^1.1.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var Errio = require('..'); 5 | 6 | exports.toObject = { 7 | testRecursiveTrue: function(ErrorClass, options) { 8 | var error = new ErrorClass('test'); 9 | error.nested = new ErrorClass('nested'); 10 | 11 | var object = Errio.toObject(error, options); 12 | assert.equal(typeof object.nested, 'object', 'contains nested error'); 13 | 14 | return object; 15 | }, 16 | 17 | testRecursiveFalse: function(ErrorClass, options) { 18 | var error = new ErrorClass('test'); 19 | error.nested = new ErrorClass('nested'); 20 | 21 | var object = Errio.toObject(error, options); 22 | assert(!object.hasOwnProperty('nested'), 'does not contain nested error'); 23 | 24 | return object; 25 | }, 26 | 27 | testInheritedTrue: function(ParentClass, ErrorClass, options) { 28 | ParentClass.prototype.parentProperty = 'inherited'; 29 | 30 | var error = new ErrorClass('test'); 31 | 32 | var object = Errio.toObject(error, options); 33 | assert.equal(object.parentProperty, 'inherited', 'contains inherited property'); 34 | 35 | return object; 36 | }, 37 | 38 | testInheritedFalse: function(ParentClass, ErrorClass, options) { 39 | ParentClass.prototype.parentProperty = 'inherited'; 40 | 41 | var error = new ErrorClass('test'); 42 | 43 | var object = Errio.toObject(error, options); 44 | assert(!object.hasOwnProperty('parentProperty'), 'does not contain inherited property'); 45 | 46 | return object; 47 | }, 48 | 49 | testStackTrue: function(ErrorClass, options) { 50 | var error = new ErrorClass('test'); 51 | var object = Errio.toObject(error, options); 52 | assert.equal(typeof object.stack, 'string', 'contains stack property'); 53 | return object; 54 | }, 55 | 56 | testStackFalse: function(ErrorClass, options) { 57 | var error = new ErrorClass('test'); 58 | var object = Errio.toObject(error, options); 59 | assert(!object.hasOwnProperty('stack'), 'does not contain stack property'); 60 | return object; 61 | }, 62 | 63 | testPrivateTrue: function(ErrorClass, options) { 64 | var error = new ErrorClass('test'); 65 | error._leading = 'private'; 66 | error.trailing_ = 'private'; 67 | 68 | var object = Errio.toObject(error, options); 69 | assert.equal(object._leading, 'private', 'contains leading underscore property'); 70 | assert.equal(object.trailing_, 'private', 'contains trailing underscore property'); 71 | 72 | return object; 73 | }, 74 | 75 | testPrivateFalse: function(ErrorClass, options) { 76 | var error = new ErrorClass('test'); 77 | error._leading = 'private'; 78 | error.trailing_ = 'private'; 79 | 80 | var object = Errio.toObject(error, options); 81 | assert(!object.hasOwnProperty('_leading'), 'does not contain leading underscore property'); 82 | assert(!object.hasOwnProperty('trailing_'), 'does not contain trailing underscore property'); 83 | 84 | return object; 85 | }, 86 | 87 | testExcludeProperty: function(property, ErrorClass, options) { 88 | var error = new ErrorClass('test'); 89 | error[property] = 'excluded'; 90 | 91 | var object = Errio.toObject(error, options); 92 | assert(!object.hasOwnProperty(property), 'does not contain excluded property'); 93 | 94 | return object; 95 | }, 96 | 97 | testIncludeProperty: function(property, ErrorClass, options) { 98 | var error = new ErrorClass('test'); 99 | error[property] = 'excluded'; 100 | 101 | var object = Errio.toObject(error, options); 102 | assert.equal(object[property], 'excluded', 'contains excluded property'); 103 | 104 | return object; 105 | } 106 | }; 107 | 108 | exports.fromObject = { 109 | testRecursiveTrue: function(ErrorClass, options) { 110 | var original = new ErrorClass('test'); 111 | original.nested = new ErrorClass('nested'); 112 | 113 | var object = Errio.toObject(original, { recursive: true }); 114 | 115 | var error = Errio.fromObject(object, options); 116 | assert(error.nested instanceof Error, 'contains nested Error'); 117 | 118 | return error; 119 | }, 120 | 121 | testRecursiveFalse: function(ErrorClass, options) { 122 | var original = new ErrorClass('test'); 123 | original.nested = new ErrorClass('nested'); 124 | 125 | var object = Errio.toObject(original, { recursive: true }); 126 | 127 | var error = Errio.fromObject(object, options); 128 | assert.equal(typeof error.nested, 'object', 'contains nested object'); 129 | assert(!(error.nested instanceof Error), 'nested object is not an Error'); 130 | 131 | return error; 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /test/from-object-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var SuperError = require('super-error'); 6 | var Errio = require('..'); 7 | 8 | var common = require('./common'); 9 | 10 | describe('fromObject', function() { 11 | describe('with registered error class', function() { 12 | it('deserializes to an instance', function() { 13 | var TestError = SuperError.subclass('FromObjectDeserializationTestError'); 14 | Errio.register(TestError); 15 | 16 | var object = Errio.toObject(new TestError('test')); 17 | var error = Errio.fromObject(object); 18 | assert(error instanceof TestError, 'is instance of error class'); 19 | assert.equal(error.name, 'FromObjectDeserializationTestError', 'has name property'); 20 | assert.equal(error.message, 'test', 'has message property'); 21 | }); 22 | }); 23 | 24 | describe('with option overrides', function() { 25 | it('sets recursive option', function() { 26 | var TestError = SuperError.subclass('FromObjectRecursiveOptionTestError'); 27 | Errio.register(TestError); 28 | 29 | common.fromObject.testRecursiveTrue(TestError, { recursive: true }); 30 | common.fromObject.testRecursiveFalse(TestError, { recursive: false }); 31 | }); 32 | }); 33 | 34 | describe('with unregistered error class', function() { 35 | it('returns Error instance with name set', function() { 36 | var object = { name: 'FromObjectUnregisteredTestError', message: 'test' }; 37 | var error = Errio.fromObject(object); 38 | assert(error instanceof Error, 'is instance of Error'); 39 | assert.equal(error.name, 'FromObjectUnregisteredTestError'); 40 | }); 41 | }); 42 | 43 | describe('without serialized stack', function() { 44 | it('captures a new stack', function() { 45 | var TestError = SuperError.subclass('FromObjectNoStackTestError'); 46 | Errio.register(TestError, { stack: false }); 47 | 48 | var object = Errio.toObject(new TestError('test')); 49 | var error = Errio.fromObject(object); 50 | assert.equal(typeof error.stack, 'string', 'has stack property'); 51 | }); 52 | }); 53 | 54 | describe('with serialized stack', function() { 55 | it('preserves stack', function() { 56 | var TestError = SuperError.subclass('FromObjectStackTestError'); 57 | Errio.register(TestError, { stack: true }); 58 | 59 | var object = Errio.toObject(new TestError('test')); 60 | var error = Errio.fromObject(object); 61 | assert.equal(error.stack, object.stack); 62 | }); 63 | }); 64 | 65 | describe('with built-in error classes', function() { 66 | it('returns Error instance', function() { 67 | var error = Errio.fromObject({ name: 'Error', message: 'test' }); 68 | assert(error instanceof Error); 69 | }); 70 | 71 | it('returns EvalError instance', function() { 72 | var error = Errio.fromObject({ name: 'EvalError', message: 'test' }); 73 | assert(error instanceof EvalError); 74 | }); 75 | 76 | it('returns RangeError instance', function() { 77 | var error = Errio.fromObject({ name: 'RangeError', message: 'test' }); 78 | assert(error instanceof RangeError); 79 | }); 80 | 81 | it('returns ReferenceError instance', function() { 82 | var error = Errio.fromObject({ name: 'ReferenceError', message: 'test' }); 83 | assert(error instanceof ReferenceError); 84 | }); 85 | 86 | it('returns SyntaxError instance', function() { 87 | var error = Errio.fromObject({ name: 'SyntaxError', message: 'test' }); 88 | assert(error instanceof SyntaxError); 89 | }); 90 | 91 | it('returns TypeError instance', function() { 92 | var error = Errio.fromObject({ name: 'TypeError', message: 'test' }); 93 | assert(error instanceof TypeError); 94 | }); 95 | 96 | it('returns URIError instance', function() { 97 | var error = Errio.fromObject({ name: 'URIError', message: 'test' }); 98 | assert(error instanceof URIError); 99 | }); 100 | }); 101 | 102 | describe('with nested plain object', function() { 103 | it('preserves object', function() { 104 | var error = Errio.fromObject({ 105 | name: 'Error', 106 | message: 'test', 107 | nested: { key: 'value' } 108 | }); 109 | assert.deepEqual(error.nested, { key: 'value' }); 110 | }); 111 | }); 112 | 113 | describe('with null property value', function() { 114 | it('does not try to recurse', function() { 115 | var error = Errio.fromObject({ 116 | name: 'Error', 117 | message: 'test', 118 | nullValue: null 119 | }); 120 | assert.equal(error.nullValue, null); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -------------------------------------------------------------------------------- /test/parse-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var SuperError = require('super-error'); 6 | var Errio = require('..'); 7 | 8 | describe('parse', function() { 9 | it('deserializes from a JSON object', function() { 10 | var error = Errio.parse('{"name":"Error","message":"test"}'); 11 | assert(error instanceof Error, 'is instance of Error'); 12 | assert.equal(error.message, 'test', 'has message property'); 13 | }); 14 | 15 | it('passes option overrides', function() { 16 | var error = Errio.parse( 17 | '{"name":"Error","message":"test","nested":{"name":"Error","message":"nested"}}', 18 | { recursive: false } 19 | ); 20 | assert.equal(typeof error.nested, 'object', 'contains nested object'); 21 | assert(!(error.nested instanceof Error), 'nested object is not an Error'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/register-all-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SuperError = require('super-error'); 4 | var Errio = require('..'); 5 | 6 | var common = require('./common'); 7 | 8 | describe('registerAll', function() { 9 | it('sets option overrides for all error classes', function() { 10 | var FirstError = SuperError.subclass('RegisterAllFirstTestError'); 11 | var SecondError = SuperError.subclass('RegisterAllSecondTestError'); 12 | Errio.registerAll([ FirstError, SecondError ], { stack: true }); 13 | common.toObject.testStackTrue(FirstError); 14 | common.toObject.testStackTrue(SecondError); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/register-object-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SuperError = require('super-error'); 4 | var Errio = require('..'); 5 | 6 | var common = require('./common'); 7 | 8 | describe('registerObject', function() { 9 | it('sets option overrides for all error classes', function() { 10 | var object = {}; 11 | var FirstError = SuperError.subclass(object, 'RegisterObjectFirstTestError'); 12 | var SecondError = SuperError.subclass(object, 'RegisterObjectSecondTestError'); 13 | Errio.registerObject(object, { stack: true }); 14 | common.toObject.testStackTrue(FirstError); 15 | common.toObject.testStackTrue(SecondError); 16 | }); 17 | 18 | it('does not call constructors', function() { 19 | var object = {}; 20 | var ThirdError = SuperError.subclass(object, 'RegisterObjectThirdTestError', function() { 21 | assert(false, 'ThirdError constructor called'); 22 | }); 23 | var FourthError = SuperError.subclass(object, 'RegisterObjectFourthTestError', function() { 24 | assert(false, 'FourthError constructor called'); 25 | }); 26 | Errio.registerObject(object); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/register-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var SuperError = require('super-error'); 6 | var Errio = require('..'); 7 | 8 | var common = require('./common'); 9 | 10 | describe('register', function() { 11 | describe('with option overrides', function() { 12 | it('sets recursive option', function() { 13 | var TrueError = SuperError.subclass('RegisterRecursiveTrueTestError'); 14 | Errio.register(TrueError, { recursive: true }); 15 | common.toObject.testRecursiveTrue(TrueError); 16 | common.fromObject.testRecursiveTrue(TrueError); 17 | 18 | var FalseError = SuperError.subclass('RegisterRecursiveFalseTestError'); 19 | Errio.register(FalseError, { recursive: false }); 20 | common.toObject.testRecursiveFalse(FalseError); 21 | common.fromObject.testRecursiveFalse(FalseError); 22 | }); 23 | 24 | it('sets inherited option', function() { 25 | var ParentError = SuperError.subclass('RegisterInheritedParentError'); 26 | 27 | var TrueError = ParentError.subclass('RegisterInheritedTrueTestError'); 28 | Errio.register(TrueError, { inherited: true }); 29 | common.toObject.testInheritedTrue(ParentError, TrueError); 30 | 31 | var FalseError = ParentError.subclass('RegisterInheritedFalseTestError'); 32 | Errio.register(FalseError, { inherited: false }); 33 | common.toObject.testInheritedFalse(ParentError, FalseError); 34 | }); 35 | 36 | it('sets stack option', function() { 37 | var TrueError = SuperError.subclass('RegisterStackTrueTestError'); 38 | Errio.register(TrueError, { stack: true }); 39 | common.toObject.testStackTrue(TrueError); 40 | 41 | var FalseError = SuperError.subclass('RegisterStackFalseTestError'); 42 | Errio.register(FalseError, { stack: false }); 43 | common.toObject.testStackFalse(FalseError); 44 | }); 45 | 46 | it('sets private option', function() { 47 | var TrueError = SuperError.subclass('RegisterPrivateTrueTestError'); 48 | Errio.register(TrueError, { private: true }); 49 | common.toObject.testPrivateTrue(TrueError); 50 | 51 | var FalseError = SuperError.subclass('RegisterPrivateFalseTestError'); 52 | Errio.register(FalseError, { private: false }); 53 | common.toObject.testPrivateFalse(FalseError); 54 | }); 55 | 56 | it('sets exclude option', function() { 57 | var ExcludeError = SuperError.subclass('RegisterExcludeTestError'); 58 | Errio.register(ExcludeError, { exclude: [ 'excluded' ] }); 59 | common.toObject.testExcludeProperty('excluded', ExcludeError); 60 | 61 | var NoExcludeError = SuperError.subclass('RegisterNoExcludeTestError'); 62 | Errio.register(ExcludeError, { exclude: [] }); 63 | common.toObject.testIncludeProperty('excluded', NoExcludeError); 64 | }); 65 | 66 | it('sets include option', function() { 67 | var IncludeError = SuperError.subclass('RegisterIncludeTestError'); 68 | Errio.register(IncludeError, { 69 | exclude: [ 'included' ], 70 | include: [ 'included' ] 71 | }); 72 | common.toObject.testIncludeProperty('included', IncludeError); 73 | 74 | var NoIncludeError = SuperError.subclass('RegisterNoIncludeError'); 75 | Errio.register(NoIncludeError, { exclude: [ 'included' ] }); 76 | common.toObject.testExcludeProperty('included', NoIncludeError); 77 | }); 78 | }); 79 | 80 | describe('with explicit error name', function() { 81 | it('does not call constructor', function() { 82 | var TestError = SuperError.subclass('RegisterExplicitNameTestError', function() { 83 | assert(false, 'constructor not called'); 84 | }); 85 | Errio.register(TestError, { name: 'RegisterExplicitNameTestError' }); 86 | }); 87 | }); 88 | 89 | describe('with already registered error class', function() { 90 | it('replaces option overrides', function() { 91 | var TestError = SuperError.subclass('RegisterReplaceOptionsTestError'); 92 | Errio.register(TestError, { stack: false }); 93 | Errio.register(TestError, { stack: true }); 94 | common.toObject.testStackTrue(TestError); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/set-defaults-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SuperError = require('super-error'); 4 | var Errio = require('..'); 5 | 6 | var common = require('./common'); 7 | 8 | // Each of these tests is required to set the option back to its factory 9 | // default in order to not interfere with other tests. This is the reason for 10 | // the inconsistent order of true/false option tests. 11 | 12 | describe('setDefaults', function() { 13 | it('sets recursive option', function() { 14 | var TestError = SuperError.subclass('SetDefaultsRecursiveOptionTestError'); 15 | 16 | Errio.setDefaults({ recursive: false }); 17 | common.toObject.testRecursiveFalse(TestError); 18 | 19 | Errio.setDefaults({ recursive: true }); 20 | common.toObject.testRecursiveTrue(TestError); 21 | 22 | Errio.setDefaults({ recursive: false }); 23 | common.fromObject.testRecursiveFalse(TestError); 24 | 25 | Errio.setDefaults({ recursive: true }); 26 | common.fromObject.testRecursiveTrue(TestError); 27 | }); 28 | 29 | it('sets inherited option', function() { 30 | var ParentError = SuperError.subclass('SetDefaultsInheritedOptionParentError'); 31 | var TestError = ParentError.subclass('SetDefaultsInheritedOptionTestError'); 32 | 33 | Errio.setDefaults({ inherited: false }); 34 | common.toObject.testInheritedFalse(ParentError, TestError); 35 | 36 | Errio.setDefaults({ inherited: true }); 37 | common.toObject.testInheritedTrue(ParentError, TestError); 38 | }); 39 | 40 | it('sets stack option', function() { 41 | var TestError = SuperError.subclass('SetDefaultsStackOptionTestError'); 42 | 43 | Errio.setDefaults({ stack: true }); 44 | common.toObject.testStackTrue(TestError); 45 | 46 | Errio.setDefaults({ stack: false }); 47 | common.toObject.testStackFalse(TestError); 48 | }); 49 | 50 | it('sets private option', function() { 51 | var TestError = SuperError.subclass('SetDefaultsPrivateOptionTestError'); 52 | 53 | Errio.setDefaults({ private: true }); 54 | common.toObject.testPrivateTrue(TestError); 55 | 56 | Errio.setDefaults({ private: false }); 57 | common.toObject.testPrivateFalse(TestError); 58 | }); 59 | 60 | it('sets exclude option', function() { 61 | var TestError = SuperError.subclass('SetDefaultsExcludeOptionTestError'); 62 | 63 | Errio.setDefaults({ exclude: [ 'excluded' ] }); 64 | common.toObject.testExcludeProperty('excluded', TestError); 65 | 66 | Errio.setDefaults({ exclude: [] }); 67 | common.toObject.testIncludeProperty('excluded', TestError); 68 | }); 69 | 70 | it('sets include option', function() { 71 | var TestError = SuperError.subclass('SetDefaultsIncludeOptionTestError'); 72 | 73 | Errio.setDefaults({ exclude: [ 'included' ], include: [ 'included' ] }); 74 | common.toObject.testIncludeProperty('included', TestError); 75 | 76 | Errio.setDefaults({ include: [] }); 77 | common.toObject.testExcludeProperty('included', TestError); 78 | 79 | Errio.setDefaults({ exclude: [] }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /test/stringify-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var SuperError = require('super-error'); 6 | var Errio = require('..'); 7 | 8 | describe('stringify', function() { 9 | it('serializes to a JSON object', function() { 10 | var json = Errio.stringify(new Error('test')); 11 | assert.equal(typeof json, 'string'); 12 | JSON.parse(json); 13 | }); 14 | 15 | it('passes option overrides', function() { 16 | var json = Errio.stringify(new Error('test'), { stack: true }); 17 | var object = JSON.parse(json); 18 | assert.equal(typeof object.stack, 'string'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/to-object-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var SuperError = require('super-error'); 6 | var Errio = require('..'); 7 | 8 | var common = require('./common'); 9 | 10 | describe('toObject', function() { 11 | it('serializes name and message', function() { 12 | var TestError = SuperError.subclass('ToObjectSerializationTestError'); 13 | var object = Errio.toObject(new TestError('test')); 14 | assert.equal(object.name, 'ToObjectSerializationTestError'); 15 | assert.equal(object.message, 'test'); 16 | }); 17 | 18 | describe('with option overrides', function() { 19 | it('sets recursive option', function() { 20 | var TestError = SuperError.subclass('ToObjectRecursiveOptionTestError'); 21 | common.toObject.testRecursiveTrue(TestError, { recursive: true }); 22 | common.toObject.testRecursiveFalse(TestError, { recursive: false }); 23 | }); 24 | 25 | it('sets inherited option', function() { 26 | var ParentError = SuperError.subclass('ToObjectInheritedOptionParentError'); 27 | var TestError = ParentError.subclass('ToObjectInheritedOptionTestError'); 28 | common.toObject.testInheritedTrue(ParentError, TestError, { inherited: true }); 29 | common.toObject.testInheritedFalse(ParentError, TestError, { inherited: false }); 30 | }); 31 | 32 | it('sets stack option', function() { 33 | var TestError = SuperError.subclass('ToObjectStackOptionTestError'); 34 | common.toObject.testStackTrue(TestError, { stack: true }); 35 | common.toObject.testStackFalse(TestError, { stack: false }); 36 | }); 37 | 38 | it('sets private option', function() { 39 | var TestError = SuperError.subclass('ToObjectPrivateOptionTestError'); 40 | common.toObject.testPrivateTrue(TestError, { private: true }); 41 | common.toObject.testPrivateFalse(TestError, { private: false }); 42 | }); 43 | 44 | it('sets exclude option', function() { 45 | var TestError = SuperError.subclass('ToObjectExcludeOptionTestError'); 46 | common.toObject.testExcludeProperty('excluded', TestError, { 47 | exclude: [ 'excluded' ] 48 | }); 49 | common.toObject.testIncludeProperty('excluded', TestError, { exclude: [] }); 50 | }); 51 | 52 | it('sets include option', function() { 53 | var TestError = SuperError.subclass('ToObjectIncludeOptionTestError'); 54 | common.toObject.testIncludeProperty('included', TestError, { 55 | exclude: [ 'included' ], 56 | include: [ 'included' ] 57 | }); 58 | common.toObject.testExcludeProperty('included', TestError, { 59 | exclude: [ 'included' ], 60 | include: [] 61 | }); 62 | }); 63 | }); 64 | 65 | describe('with unregistered error class', function() { 66 | it('registers error class with option overrides', function() { 67 | var TestError = SuperError.subclass('ToObjectImplicitRegisterTestError'); 68 | Errio.toObject(new TestError('test'), { stack: true }); 69 | common.toObject.testStackTrue(TestError); 70 | }); 71 | }); 72 | 73 | describe('with explicitly set stack property', function() { 74 | it('does not include stack', function() { 75 | var TestError = SuperError.subclass('ToObjectExplicitStackTestError'); 76 | var error = new TestError('test'); 77 | delete error.stack; 78 | error.stack = 'bogus'; 79 | 80 | var object = Errio.toObject(error, { stack: false }); 81 | assert(!object.hasOwnProperty('stack'), 'does not contain stack property'); 82 | }); 83 | }); 84 | 85 | describe('with null property value', function() { 86 | it('does not try to recurse', function() { 87 | var TestError = SuperError.subclass('ToObjectNullPropertyValueTestError'); 88 | var error = new TestError('test'); 89 | error.nullValue = null; 90 | 91 | var object = Errio.toObject(error, { recursive: true }); 92 | assert.equal(object.nullValue, null); 93 | }); 94 | }); 95 | }); 96 | --------------------------------------------------------------------------------