├── .gitignore ├── examples ├── stack.js ├── sync-uncaught-exception.js ├── async-uncaught-exception.js ├── handling-streams.js └── custom-error.js ├── .travis.yml ├── test ├── fixtures.js ├── macros.js └── errs-test.js ├── package.json ├── LICENSE ├── lib └── errs.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log -------------------------------------------------------------------------------- /examples/stack.js: -------------------------------------------------------------------------------- 1 | var errs = require('../lib/errs'); 2 | 3 | console.log( 4 | errs.create('This is an error. There are many like it. It has a transparent stack trace.') 5 | .stack 6 | .split('\n') 7 | ); -------------------------------------------------------------------------------- /examples/sync-uncaught-exception.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | errs = require('../lib/errs'); 3 | 4 | process.on('uncaughtException', function(err) { 5 | console.log(errs.merge(err, {namespace: 'uncaughtException'})); 6 | }); 7 | 8 | var file = fs.createReadStream('FileDoesNotExist.here'); 9 | -------------------------------------------------------------------------------- /examples/async-uncaught-exception.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | errs = require('../lib/errs'); 3 | 4 | process.on('uncaughtException', function(err) { 5 | console.log(errs.merge(err, {namespace: 'uncaughtException'})); 6 | }); 7 | 8 | var file = fs.createReadStream(__filename, {encoding: 'utf8'}); 9 | file.on('data', function(b) { throw new Error('Oh Noes'); }); 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "stable" 5 | - 4.2 6 | - 0.12 7 | - 0.10 8 | - 0.8 9 | 10 | before_install: 11 | - travis_retry npm install -g npm@2.14.5 12 | - travis_retry npm install 13 | 14 | script: 15 | - npm test 16 | 17 | matrix: 18 | allow_failures: 19 | - node_js: "0.8" 20 | 21 | notifications: 22 | email: 23 | - charlie.robbins@gmail.com 24 | 25 | -------------------------------------------------------------------------------- /examples/handling-streams.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | errs = require('../lib/errs'); 3 | 4 | function safeReadStream(no_such_file, callback) { 5 | try { 6 | return fs.createReadStream(no_such_file, callback); 7 | } catch (err) { 8 | return errs.handle(err, callback); 9 | } 10 | } 11 | 12 | // would throw, now even without a callback it gets picked as a stream 13 | var file = fs.createReadStream('FileDoesNotExist.here'); 14 | file.on('error', function (err) { console.log(err); }); -------------------------------------------------------------------------------- /examples/custom-error.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | errs = require('../lib/errs'); 3 | 4 | function MyError () { 5 | this.message = 'This is my error; I made it myself. It has a transparent stack trace.'; 6 | } 7 | 8 | // 9 | // Alternatively `MyError.prototype.__proto__ = Error;` 10 | // 11 | util.inherits(MyError, Error); 12 | 13 | // 14 | // Register the error type 15 | // 16 | errs.register('myerror', MyError); 17 | 18 | console.log( 19 | errs.create('myerror') 20 | .stack 21 | .split('\n') 22 | ); -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | /* 2 | * fixtures.js: Test fixtures for the `errs` module. 3 | * 4 | * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. 5 | * MIT LICENSE 6 | * 7 | */ 8 | 9 | var util = require('util'); 10 | 11 | var fixtures = exports; 12 | 13 | fixtures.NamedError = function NamedError() { 14 | this.named = true; 15 | }; 16 | 17 | util.inherits(fixtures.NamedError, Error); 18 | 19 | fixtures.AnError = function AnError() { 20 | this.named = true; 21 | }; 22 | 23 | util.inherits(fixtures.AnError, Error); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "errs", 3 | "description": "Simple error creation and passing utilities", 4 | "version": "0.3.2", 5 | "author": "Charlie Robbins ", 6 | "maintainers": [ 7 | "dscape " 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "http://github.com/indexzero/errs.git" 12 | }, 13 | "keywords": [ 14 | "errors", 15 | "error", 16 | "utilities" 17 | ], 18 | "devDependencies": { 19 | "vows": "0.8.x" 20 | }, 21 | "main": "./lib/errs", 22 | "scripts": { 23 | "test": "vows test/*-test.js --spec" 24 | }, 25 | "engines": { 26 | "node": ">= 0.4.0" 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Charlie Robbins, Nuno Job, and the Contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/macros.js: -------------------------------------------------------------------------------- 1 | /* 2 | * macros.js: Test macros for the `errs` module. 3 | * 4 | * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. 5 | * MIT LICENSE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | errs = require('../lib/errs'); 11 | 12 | var macros = exports; 13 | 14 | function assertTransparentStack(err) { 15 | assert.isString(err.stack); 16 | err.stack.split('\n').forEach(function (line) { 17 | assert.isFalse(/\/lib\/errs\.js\:/.test(line)); 18 | }); 19 | } 20 | 21 | // 22 | // Macros for `errs.create(type, opts)`. 23 | // 24 | macros.create = {}; 25 | 26 | macros.create.string = function (msg) { 27 | return { 28 | topic: errs.create(msg), 29 | "should create an error with the correct message": function (err) { 30 | assert.instanceOf(err, Error); 31 | assert.equal(msg, err.message); 32 | assertTransparentStack(err); 33 | } 34 | }; 35 | }; 36 | 37 | macros.create.object = function (obj) { 38 | return { 39 | topic: errs.create(obj), 40 | "should create an error with the specified properties": function (err) { 41 | assert.instanceOf(err, Error); 42 | assert.equal(err.message, obj.message || 'Unspecified error'); 43 | assertTransparentStack(err); 44 | Object.keys(obj).forEach(function (key) { 45 | assert.equal(err[key], obj[key]); 46 | }); 47 | } 48 | }; 49 | }; 50 | 51 | macros.create.err = function (inst) { 52 | return { 53 | topic: errs.create(inst), 54 | "should return the error unmodified": function (err) { 55 | assert.equal(err, inst); 56 | assertTransparentStack(err); 57 | } 58 | }; 59 | }; 60 | 61 | macros.create.fn = function (fn) { 62 | var obj = fn(); 63 | 64 | return { 65 | topic: errs.create(fn), 66 | "should create an error with the specified properties": function (err) { 67 | assert.instanceOf(err, Error); 68 | assert.equal(err.message, obj.message || 'Unspecified error'); 69 | assertTransparentStack(err); 70 | Object.keys(obj).forEach(function (key) { 71 | assert.equal(err[key], obj[key]); 72 | }); 73 | } 74 | }; 75 | }; 76 | 77 | macros.create.registered = function (type, proto, obj) { 78 | return { 79 | topic: function () { 80 | return errs.create(type, obj); 81 | }, 82 | "should create an error of the correct type": function (err) { 83 | assert.instanceOf(err, proto || Error); 84 | assert.equal(err.message, obj.message || 'Unspecified error'); 85 | assertTransparentStack(err); 86 | Object.keys(obj).forEach(function (key) { 87 | assert.equal(err[key], obj[key]); 88 | }); 89 | } 90 | }; 91 | }; -------------------------------------------------------------------------------- /test/errs-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * errs-test.js: Tests for the `errs` module. 3 | * 4 | * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. 5 | * MIT LICENSE 6 | * 7 | */ 8 | 9 | var assert = require('assert'), 10 | events = require('events'), 11 | vows = require('vows'), 12 | errs = require('../lib/errs'), 13 | fixtures = require('./fixtures'), 14 | macros = require('./macros'); 15 | 16 | var opts = [{ 17 | foo: 'bar', 18 | status: 404, 19 | whatever: 'some other property' 20 | }, { 21 | testing: true, 22 | 'some-string': 'is-a-value', 23 | message: 'This is an error. There are many like it.' 24 | }, { 25 | 'a-function': 'that returns an object', 26 | should: true, 27 | have: 4, 28 | properties: 'yes' 29 | }]; 30 | 31 | vows.describe('errs').addBatch({ 32 | "Using errs module": { 33 | "the register() method": { 34 | "should register the prototype": function () { 35 | errs.register('named', fixtures.NamedError); 36 | assert.equal(errs.registered['named'], fixtures.NamedError); 37 | }, 38 | "should register an error without providing its name": function () { 39 | errs.register(fixtures.AnError); 40 | assert.equal(errs.registered['anerror'], fixtures.AnError); 41 | } 42 | }, 43 | "the create() method with": { 44 | "a string": macros.create.string('An error as a string'), 45 | "no parameters": macros.create.string('An error as a string'), 46 | "an object": { 47 | "that has no message": macros.create.object(opts[0]), 48 | "that has a message": macros.create.object(opts[1]), 49 | "that has a name": { 50 | topic : errs.create({name: 'OverflowError'}), 51 | "should respect that name in the stack trace" : function (err) { 52 | assert.match(err.stack, /^OverflowError/); 53 | }, 54 | } 55 | }, 56 | "an error": macros.create.err(new Error('An instance of an error')), 57 | "a function": macros.create.fn(function () { 58 | return opts[2]; 59 | }), 60 | "a registered type": { 61 | "that exists": macros.create.registered('named', fixtures.NamedError, opts[1]), 62 | "that doesnt exist": macros.create.registered('bad', null, opts[1]) 63 | } 64 | }, 65 | "the handle() method": { 66 | "with a callback": { 67 | topic: function () { 68 | var err = this.err = errs.create('Some async error'); 69 | errs.handle(err, this.callback.bind(this, null)); 70 | }, 71 | "should invoke the callback with the error": function (_, err) { 72 | assert.equal(err, this.err); 73 | } 74 | }, 75 | "with an EventEmitter (i.e. stream)": { 76 | topic: function () { 77 | var err = this.err = errs.create('Some emitted error'), 78 | stream = new events.EventEmitter(); 79 | 80 | stream.once('error', this.callback.bind(this, null)); 81 | errs.handle(err, stream); 82 | }, 83 | "should emit the `error` event": function (_, err) { 84 | assert.equal(err, this.err); 85 | } 86 | }, 87 | "with a callback and an EventEmitter": { 88 | topic: function () { 89 | var err = this.err = errs.create('Some emitted error'), 90 | stream = new events.EventEmitter(), 91 | invoked = 0, 92 | that = this; 93 | 94 | function onError(err) { 95 | if (++invoked === 2) { 96 | that.callback.call(that, null, err); 97 | } 98 | } 99 | 100 | stream.once('error', onError); 101 | errs.handle(err, onError, stream); 102 | }, 103 | "should emit the `error` event": function (_, err) { 104 | assert.equal(err, this.err); 105 | } 106 | }, 107 | "with no callback": { 108 | topic: function () { 109 | var err = this.err = errs.create('Some emitted error'), 110 | emitter = errs.handle(err); 111 | 112 | emitter.once('error', this.callback.bind(this, null)); 113 | }, 114 | "should emit the `error` event": function (_, err) { 115 | assert.equal(err, this.err); 116 | } 117 | } 118 | } 119 | } 120 | }).addBatch({ 121 | "Using errs module": { 122 | "the unregister() method": { 123 | "should unregister the prototype": function () { 124 | errs.unregister('named'); 125 | assert.isTrue(!errs.registered['named']); 126 | } 127 | } 128 | } 129 | }).addBatch({ 130 | "Using errs module": { 131 | "the merge() method": { 132 | "supports": { 133 | "an undefined error": function () { 134 | var err = errs.merge(undefined, { message: 'oh noes!' }); 135 | assert.equal(err.message, 'oh noes!') 136 | assert.instanceOf(err, Error); 137 | }, 138 | "a null error": function () { 139 | var err = errs.merge(null, { message: 'oh noes!' }); 140 | assert.equal(err.message, 'oh noes!') 141 | assert.instanceOf(err, Error); 142 | }, 143 | "a false error": function () { 144 | var err = errs.merge(false, { message: 'oh noes!' }); 145 | assert.equal(err.message, 'oh noes!') 146 | assert.instanceOf(err, Error); 147 | }, 148 | "a string error": function () { 149 | var err = errs.merge('wat', { message: 'oh noes!' }); 150 | assert.equal(err.message, 'oh noes!'); 151 | assert.instanceOf(err, Error); 152 | }, 153 | }, 154 | "should preserve custom properties": function () { 155 | var err = new Error('Msg!'); 156 | err.foo = "bar"; 157 | err = errs.merge(err, {message: "Override!", ns: "test"}); 158 | assert.equal(err.foo, "bar"); 159 | }, 160 | "should have a stack trace": function () { 161 | var err = new Error('Msg!'); 162 | err = errs.merge(err, {}); 163 | assert.isTrue(Array.isArray(err.stacktrace)); 164 | }, 165 | "should preserve message specified in create": function () { 166 | var err = new Error('Msg!'); 167 | err = errs.merge(err, {message: "Override!"}); 168 | assert.equal(err.message, "Override!"); 169 | }, 170 | "should preserve properties specified": function () { 171 | var err = new Error('Msg!'); 172 | err = errs.merge(err, {ns: "test"}); 173 | assert.equal(err.ns, "test"); 174 | }, 175 | "with a truthy value": function () { 176 | var err = errs.merge(true, { 177 | message: 'Override!', 178 | ns: 'lolwut' 179 | }) 180 | assert.equal(err.message, 'Override!'); 181 | assert.equal(err.ns, 'lolwut'); 182 | }, 183 | "with a truthy stack": function () { 184 | var err = errs.merge({ stack: true } , { 185 | message: 'Override!', 186 | ns: 'lolwut' 187 | }) 188 | assert.equal(err.message, 'Override!'); 189 | assert.equal(err.ns, 'lolwut'); 190 | }, 191 | "with an Array stack": function () { 192 | var err = errs.merge({ stack: [] } , { 193 | message: 'Override!', 194 | ns: 'lolwut' 195 | }) 196 | assert.equal(err.message, 'Override!'); 197 | assert.equal(err.ns, 'lolwut'); 198 | } 199 | } 200 | } 201 | }).addBatch({ 202 | "Using errs module": { 203 | "Error.prototype.toJSON": { 204 | "should exist": function () { 205 | assert.isFunction(Error.prototype.toJSON); 206 | 207 | var json = (new Error('Testing 12345')).toJSON(); 208 | 209 | ['message', 'stack', 'arguments', 'type'].forEach(function (prop) { 210 | assert.isObject(Object.getOwnPropertyDescriptor(json, prop)); 211 | }) 212 | }, 213 | "should be writable": function () { 214 | var orig = Error.prototype.toJSON; 215 | Error.prototype.toJSON = function() { 216 | return 'foo'; 217 | }; 218 | var json = (new Error('Testing 12345')).toJSON(); 219 | 220 | assert.equal(json, 'foo'); 221 | Error.prototype.toJSON = orig; 222 | } 223 | } 224 | } 225 | }).export(module); 226 | -------------------------------------------------------------------------------- /lib/errs.js: -------------------------------------------------------------------------------- 1 | /* 2 | * errs.js: Simple error creation and passing utilities. 3 | * 4 | * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. 5 | * MIT LICENSE 6 | * 7 | */ 8 | 9 | var events = require('events'), 10 | util = require('util'); 11 | 12 | // 13 | // Container for registered error types. 14 | // 15 | exports.registered = {}; 16 | 17 | // 18 | // Add `Error.prototype.toJSON` if it doesn't exist. 19 | // 20 | if (!Error.prototype.toJSON) { 21 | Object.defineProperty(Error.prototype, 'toJSON', { 22 | enumerable: false, 23 | writable: true, 24 | value: function () { 25 | return mixin({ 26 | message: this.message, 27 | stack: this.stack, 28 | arguments: this.arguments, 29 | type: this.type 30 | }, this); 31 | } 32 | }); 33 | } 34 | 35 | // 36 | // ### function create (type, opts) 37 | // #### @type {string} **Optional** Registered error type to create 38 | // #### @opts {string|object|Array|function} Options for creating the error: 39 | // * `string`: Message for the error 40 | // * `object`: Properties to include on the error 41 | // * `array`: Message for the error (' ' joined). 42 | // * `function`: Function to return error options. 43 | // 44 | // Creates a new error instance for with the specified `type` 45 | // and `options`. If the `type` is not registered then a new 46 | // `Error` instance will be created. 47 | // 48 | exports.create = function createErr(type, opts) { 49 | if (!arguments[1] && !exports.registered[type]) { 50 | opts = type; 51 | type = null; 52 | } 53 | 54 | // 55 | // If the `opts` has a `stack` property assume 56 | // that it is already an error instance. 57 | // 58 | if (opts && opts.stack) { 59 | return opts; 60 | } 61 | 62 | var message, 63 | ErrorProto, 64 | error; 65 | 66 | // 67 | // Parse arguments liberally for the message 68 | // 69 | if (typeof opts === 'function') { 70 | opts = opts(); 71 | } 72 | 73 | if (Array.isArray(opts)) { 74 | message = opts.join(' '); 75 | opts = null; 76 | } 77 | else if (opts) { 78 | switch (typeof opts) { 79 | case 'string': 80 | message = opts || 'Unspecified error'; 81 | opts = null; 82 | break; 83 | case 'object': 84 | message = (opts && opts.message) || 'Unspecified error'; 85 | break; 86 | default: 87 | message = 'Unspecified error'; 88 | break; 89 | } 90 | } 91 | 92 | // 93 | // Instantiate a new Error instance or a new 94 | // registered error type (if it exists). 95 | // 96 | ErrorProto = type && exports.registered[type] || Error; 97 | error = new (ErrorProto)(message); 98 | 99 | if (!error.name || error.name === 'Error') { 100 | error.name = (opts && opts.name) || ErrorProto.name || 'Error'; 101 | } 102 | 103 | // 104 | // Capture a stack trace if it does not already exist and 105 | // remote the part of the stack trace referencing `errs.js`. 106 | // 107 | if (!error.stack) { 108 | Error.call(error); 109 | Error.captureStackTrace(error, createErr); 110 | } 111 | else { 112 | error.stack = error.stack.split('\n'); 113 | error.stack.splice(1, 1); 114 | error.stack = error.stack.join('\n'); 115 | } 116 | 117 | // 118 | // Copy all options to the new error instance. 119 | // 120 | if (opts) { 121 | Object.keys(opts).forEach(function (key) { 122 | error[key] = opts[key]; 123 | }); 124 | } 125 | 126 | return error; 127 | }; 128 | 129 | // 130 | // ### function merge (err, type, opts) 131 | // #### @err {error} **Optional** The error to merge 132 | // #### @type {string} **Optional** Registered error type to create 133 | // #### @opts {string|object|Array|function} Options for creating the error: 134 | // * `string`: Message for the error 135 | // * `object`: Properties to include on the error 136 | // * `array`: Message for the error (' ' joined). 137 | // * `function`: Function to return error options. 138 | // 139 | // Merges an existing error with a new error instance for with 140 | // the specified `type` and `options`. 141 | // 142 | exports.merge = function (err, type, opts) { 143 | var merged = exports.create(type, opts); 144 | 145 | // 146 | // If there is no error just return the merged one 147 | // 148 | if (err == undefined || err == null) { 149 | return merged; 150 | } 151 | 152 | // 153 | // optional stuff that might be created by module 154 | // 155 | if (!Array.isArray(err) && typeof err === 'object') { 156 | Object.keys(err).forEach(function (key) { 157 | // 158 | // in node v0.4 v8 errors where treated differently 159 | // we need to make sure we aren't merging these properties 160 | // http://code.google.com/p/v8/issues/detail?id=1215 161 | // 162 | if (['stack', 'type', 'arguments', 'message'].indexOf(key)===-1) { 163 | merged[key] = err[key]; 164 | } 165 | }); 166 | } 167 | 168 | // merging 169 | merged.name = merged.name || err.name; 170 | merged.message = merged.message || err.message; 171 | 172 | // override stack 173 | merged.stack = err.stack || merged.stack; 174 | 175 | // add human-readable errors 176 | if (err.message) { 177 | merged.description = err.message; 178 | } 179 | 180 | if (err.stack && err.stack.split) { 181 | merged.stacktrace = err.stack.split("\n"); 182 | } 183 | 184 | return merged; 185 | }; 186 | 187 | // 188 | // ### function handle (error, callback) 189 | // #### @error {string|function|Array|object} Error to handle 190 | // #### @callback {function|EventEmitter} **Optional** Continuation or stream to pass the error to. 191 | // #### @stream {EventEmitter} **Optional** Explicit EventEmitter to use. 192 | // 193 | // Attempts to instantiate the given `error`. If the `error` is already a properly 194 | // formed `error` object (with a `stack` property) it will not be modified. 195 | // 196 | // * If `callback` is a function, it is invoked with the `error`. 197 | // * If `callback` is an `EventEmitter`, it emits the `error` event on 198 | // that emitter and returns it. 199 | // * If no `callback`, return a new `EventEmitter` which emits `error` 200 | // on `process.nextTick()`. 201 | // 202 | exports.handle = function (error, callback, stream) { 203 | error = exports.create(error); 204 | 205 | if (typeof callback === 'function') { 206 | callback(error); 207 | } 208 | 209 | if (typeof callback !== 'function' || stream) { 210 | var emitter = stream || callback || new events.EventEmitter(); 211 | process.nextTick(function () { emitter.emit('error', error); }); 212 | return emitter; 213 | } 214 | }; 215 | 216 | // 217 | // ### function register (type, proto) 218 | // #### @type {string} **Optional** Type of the error to register. 219 | // #### @proto {function} Constructor function of the error to register. 220 | // 221 | // Registers the specified `proto` to `type` for future calls to 222 | // `errors.create(type, opts)`. 223 | // 224 | exports.register = function (type, proto) { 225 | if (arguments.length === 1) { 226 | proto = type; 227 | type = proto.name.toLowerCase(); 228 | } 229 | exports.registered[type] = proto; 230 | }; 231 | 232 | // 233 | // ### function unregister (type) 234 | // #### @type {string} Type of the error to unregister. 235 | // 236 | // Unregisters the specified `type` for future calls to 237 | // `errors.create(type, opts)`. 238 | // 239 | exports.unregister = function (type) { 240 | delete exports.registered[type]; 241 | }; 242 | 243 | // 244 | // ### function mixin (target [source0, source1, ...]) 245 | // Copies enumerable properties from `source0 ... sourceN` 246 | // onto `target` and returns the resulting object. 247 | // 248 | function mixin(target) { 249 | // 250 | // Quickly and performantly (in V8) `Arrayify` arguments. 251 | // 252 | var len = arguments.length, 253 | args = new Array(len - 1), 254 | i = 1; 255 | 256 | for (; i < len; i++) { 257 | args[i - 1] = arguments[i]; 258 | } 259 | 260 | args.forEach(function (o) { 261 | Object.keys(o).forEach(function (attr) { 262 | var getter = o.__lookupGetter__(attr), 263 | setter = o.__lookupSetter__(attr); 264 | 265 | if (!getter && !setter) { 266 | target[attr] = o[attr]; 267 | } 268 | else { 269 | if (setter) { target.__defineSetter__(attr, setter) } 270 | if (getter) { target.__defineGetter__(attr, getter) } 271 | } 272 | }); 273 | }); 274 | 275 | return target; 276 | } 277 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # errs [![Build Status](https://secure.travis-ci.org/indexzero/errs.png)](http://travis-ci.org/indexzero/errs) 2 | 3 | Simple error creation and passing utilities focused on: 4 | 5 | * [Creating Errors](#creating-errors) 6 | * [Reusing Error Types](#reusing-types) 7 | * [Merging with Existing Errors](#merging-errors) 8 | * [Optional Callback Invocation](#optional-invocation) 9 | * [Piping Error Events](#piping-errors) 10 | 11 | 12 | ## Creating Errors 13 | 14 | You should know by now that [a String is not an Error][0]. Unfortunately the `Error` constructor in Javascript isn't all that convenient either. How often do you find yourself in this situation? 15 | 16 | ``` js 17 | var err = new Error('This is an error. There are many like it.'); 18 | err.someProperty = 'more syntax'; 19 | err.someOtherProperty = 'it wont stop.'; 20 | err.notEven = 'for the mayor'; 21 | 22 | throw err; 23 | ``` 24 | 25 | Rest your fingers, `errs` is here to help. The following is equivalent to the above: 26 | 27 | ``` js 28 | var errs = require('errs'); 29 | 30 | throw errs.create({ 31 | message: 'This is an error. There are many like it.', 32 | someProperty: 'more syntax', 33 | someOtherProperty: 'it wont stop.', 34 | notEven: 'for the mayor' 35 | }); 36 | ``` 37 | 38 | 39 | ## Reusing Custom Error Types 40 | 41 | `errs` also exposes an [inversion of control][1] interface for easily reusing custom error types across your application. Custom Error Types registered with `errs` will transparently invoke `Error` constructor and `Error.captureStackTrace` to attach transparent stack traces: 42 | 43 | ``` js 44 | /* 45 | * file-a.js: Create and register your error type. 46 | * 47 | */ 48 | 49 | var util = require('util'), 50 | errs = require('errs'); 51 | 52 | function MyError() { 53 | this.message = 'This is my error; I made it myself. It has a transparent stack trace.'; 54 | } 55 | 56 | // 57 | // Alternatively `MyError.prototype.__proto__ = Error;` 58 | // 59 | util.inherits(MyError, Error); 60 | 61 | // 62 | // Register the error type 63 | // 64 | errs.register('myerror', MyError); 65 | 66 | 67 | 68 | /* 69 | * file-b.js: Use your error type. 70 | * 71 | */ 72 | 73 | var errs = require('errs'); 74 | 75 | console.log( 76 | errs.create('myerror') 77 | .stack 78 | .split('\n') 79 | ); 80 | ``` 81 | 82 | The output from the two files above is shown below. Notice how it contains no references to `errs.js`: 83 | 84 | ``` 85 | [ 'MyError: This is my error; I made it myself. It has a transparent stack trace.', 86 | ' at Object. (/file-b.js:19:8)', 87 | ' at Module._compile (module.js:441:26)', 88 | ' at Object..js (module.js:459:10)', 89 | ' at Module.load (module.js:348:31)', 90 | ' at Function._load (module.js:308:12)', 91 | ' at Array.0 (module.js:479:10)', 92 | ' at EventEmitter._tickCallback (node.js:192:40)' ] 93 | ``` 94 | 95 | 96 | ## Merging with Existing Errors 97 | 98 | When working with errors you catch or are returned in a callback you can extend those errors with properties by using the `errs.merge` method. This will also create a human readable error message and stack-trace: 99 | 100 | ``` js 101 | process.on('uncaughtException', function(err) { 102 | console.log(errs.merge(err, {namespace: 'uncaughtException'})); 103 | }); 104 | 105 | var file = fs.createReadStream('FileDoesNotExist.here'); 106 | ``` 107 | 108 | ``` js 109 | { [Error: Unspecified error] 110 | name: 'Error', 111 | namespace: 'uncaughtException', 112 | errno: 34, 113 | code: 'ENOENT', 114 | path: 'FileDoesNotExist.here', 115 | description: 'ENOENT, no such file or directory \'FileDoesNotExist.here\'', 116 | stacktrace: [ 'Error: ENOENT, no such file or directory \'FileDoesNotExist.here\'' ] } 117 | ``` 118 | 119 | 120 | ## Optional Callback Invocation 121 | 122 | Node.js handles asynchronous IO through the elegant `EventEmitter` API. In many scenarios the `callback` may be optional because you are returning an `EventEmitter` for piping or other event multiplexing. This complicates code with a lot of boilerplate: 123 | 124 | ``` js 125 | function importantFeature(callback) { 126 | return someAsyncFn(function (err) { 127 | if (err) { 128 | if (callback) { 129 | return callback(err); 130 | } 131 | 132 | throw err; 133 | } 134 | }); 135 | } 136 | ``` 137 | 138 | `errs` it presents a common API for both emitting `error` events and invoking continuations (i.e. callbacks) with errors. If a `callback` is supplied to `errs.handle()` it will be invoked with the error. It no `callback` is provided then an `EventEmitter` is returned which emits an `error` event on the next tick: 139 | 140 | ``` js 141 | function importantFeature(callback) { 142 | return someAsyncFn(function (err) { 143 | if (err) { 144 | return errs.handle(err, callback); 145 | } 146 | }); 147 | } 148 | ``` 149 | 150 | 151 | ## Piping Errors 152 | 153 | Often when working with streams (especially when buffering for whatever reason), you may have already returned an `EventEmitter` or `Stream` instance by the time an error is handled. 154 | 155 | ``` js 156 | function pipeSomething(callback) { 157 | // 158 | // You have a stream (e.g. http.ResponseStream) and you 159 | // have an optional `callback`. 160 | // 161 | var stream = new require('stream').Stream; 162 | 163 | // 164 | // You need to do something async which may respond with an 165 | // error 166 | // 167 | getAnotherStream(function (err, source) { 168 | if (err) { 169 | if (callback) 170 | callback(err); 171 | } 172 | 173 | stream.emit('error', err); 174 | return; 175 | } 176 | 177 | source.pipe(stream); 178 | }) 179 | 180 | return stream; 181 | } 182 | ``` 183 | 184 | You may pass either a `function` or `EventEmitter` instance to `errs.handle`. 185 | 186 | ``` js 187 | function pipeSomething(callback) { 188 | // 189 | // You have a stream (e.g. http.ResponseStream) and you 190 | // have an optional `callback`. 191 | // 192 | var stream = new require('stream').Stream; 193 | 194 | // 195 | // You need to do something async which may respond with an 196 | // error 197 | // 198 | getAnotherStream(function (err, source) { 199 | if (err) { 200 | // 201 | // Invoke the callback if it exists otherwise the stream. 202 | // 203 | return errs.handle(err, callback || stream); 204 | } 205 | 206 | source.pipe(stream); 207 | }) 208 | 209 | return stream; 210 | } 211 | ``` 212 | 213 | If you wish to invoke both a `callback` function and an `error` event simply pass both: 214 | 215 | ``` js 216 | errs.handle(err, callback, stream); 217 | ``` 218 | 219 | ## Methods 220 | The `errs` modules exposes some simple utility methods: 221 | 222 | * `.create(type, opts)`: Creates a new error instance for with the specified `type` and `opts`. If the `type` is not registered then a new `Error` instance will be created. 223 | * `.register(type, proto)`: Registers the specified `proto` to `type` for future calls to `errors.create(type, opts)`. 224 | * `.unregister(type)`: Unregisters the specified `type` for future calls to `errors.create(type, opts)`. 225 | * `.handle(err, callback)`: Attempts to instantiate the given `error`. If the `error` is already a properly formed `error` object (with a `stack` property) it will not be modified. 226 | * `.merge(err, type, opts)`: Merges an existing error with a new error instance for with the specified `type` and `opts`. 227 | 228 | ## Installation 229 | 230 | ### Installing npm (node package manager) 231 | 232 | ``` bash 233 | $ curl http://npmjs.org/install.sh | sh 234 | ``` 235 | 236 | ### Installing errs 237 | 238 | ``` bash 239 | $ [sudo] npm install errs 240 | ``` 241 | 242 | ## Tests 243 | All tests are written with [vows][2] and should be run with [npm][3]: 244 | 245 | ``` bash 246 | $ npm test 247 | ``` 248 | 249 | #### Author: [Charlie Robbins](http://github.com/indexzero) 250 | #### Contributors: [Nuno Job](http://github.com/dscape) 251 | #### License: MIT 252 | 253 | [0]: http://www.devthought.com/2011/12/22/a-string-is-not-an-error/ 254 | [1]: http://martinfowler.com/articles/injection.html 255 | [2]: https://vowsjs.org 256 | [3]: https://npmjs.org 257 | --------------------------------------------------------------------------------