├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lib ├── reformat.js └── v8-style.js ├── package.json └── test ├── catch.js ├── inheritance.js ├── inspect.js ├── message.js ├── new.js ├── reformat-test.js ├── throw.js └── v8-style-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Andre Z. Sanchez 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # custom-error 2 | 3 | Create custom errors that inherit Error. 4 | 5 | [![build status](https://secure.travis-ci.org/andrezsanchez/custom-error.png)](http://travis-ci.org/andrezsanchez/custom-error) 6 | 7 | ## why 8 | 9 | Extending Error is a real [pain](http://stackoverflow.com/questions/1382107/whats-a-good-way-to-extend-error-in-javascript). This library deals with the quirks, providing a clean API to extend Error that works across JS environments, including Node and browsers. 10 | 11 | ## api 12 | 13 | ### customError(name, [ParentError]) 14 | 15 | Returns a new subclass of Error, or of ParentError if it is provided. 16 | 17 | #### name 18 | 19 | *Required* 20 | Type: `string` 21 | 22 | The display name of this class's errors. For example, the builtin TypeError class's name is `"TypeError"`. This affects how the error is displayed when it is thrown. 23 | 24 | #### ParentError 25 | 26 | Type: `Error`, `Error` descendant 27 | Default: `Error` 28 | 29 | The Error type to be subclassed. 30 | 31 | ## examples 32 | 33 | ``` js 34 | var customError = require('custom-error'); 35 | 36 | var ApocalypseError = customError('ApocalypseError'); 37 | 38 | ApocalypseError() instanceof Error // true 39 | ApocalypseError() instanceof ApocalypseError // true 40 | 41 | var UnixApocalypseError = customError('UnixApocalypseError', ApocalypseError) 42 | 43 | UnixApocalypseError() instanceof Error // true 44 | UnixApocalypseError() instanceof ApocalypseError // true 45 | UnixApocalypseError() instanceof UnixApocalypseError // true 46 | 47 | if (new Date().getFullYear() === 2038) { 48 | throw UnixApocalypseError('OH NOES') 49 | } 50 | ``` 51 | 52 | ### using prototype 53 | 54 | ``` js 55 | UnixApocalypseError.prototype.year = 2038 56 | 57 | try { 58 | throw UnixApocalypseError() 59 | } 60 | catch (err) { 61 | console.log(err.year) // 2038 62 | } 63 | ``` 64 | 65 | 66 | ## installation 67 | 68 | ``` bash 69 | npm install custom-error 70 | ``` 71 | 72 | ## license 73 | 74 | MIT 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var v8StyleErrors = require('./lib/v8-style')() 4 | var reformat = require('./lib/reformat') 5 | 6 | function defaultInspect() { 7 | return this.message 8 | ? '[' + this.name + ': ' + this.message + ']' 9 | : '[' + this.name + ']' 10 | } 11 | 12 | function reformatStack() { 13 | // if we have v8-styled stack messages and this.stack is defined, then reformat 14 | if (v8StyleErrors && this.stack) { 15 | this.stack = reformat(this.stack, this.name, this.message) 16 | } 17 | } 18 | 19 | function ErrorMaker(name, ParentError) { 20 | function NewError(message) { 21 | if (!(this instanceof NewError)) 22 | return new NewError(message) 23 | 24 | // Use a try/catch block to capture the stack trace. Capturing the stack trace here is 25 | // necessary, otherwise we will get the stack trace at the time the new error class was created, 26 | // rather than when it is instantiated. We add `message` and `name` so that the stack trace 27 | // string will match our current error class. 28 | try { 29 | throw new Error(message) 30 | } 31 | catch (err) { 32 | err.name = name 33 | this.stack = err.stack 34 | } 35 | 36 | this.message = message || '' 37 | this.name = name 38 | 39 | this.reformatStack(); 40 | } 41 | 42 | NewError.prototype = new (ParentError || Error)() 43 | NewError.prototype.constructor = NewError 44 | NewError.prototype.inspect = defaultInspect; 45 | NewError.prototype.name = name 46 | NewError.prototype.reformatStack = reformatStack; 47 | 48 | return NewError 49 | } 50 | 51 | module.exports = ErrorMaker 52 | -------------------------------------------------------------------------------- /lib/reformat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Refomat the stack message to show the proper name rather than just 'Error'. This is 5 | * necessary because the internal class property of Error is what determines the beginning of 6 | * the stack message, and it is not accessible in JavaScript. 7 | */ 8 | module.exports = function reformatV8Error(stack, name, msg) { 9 | var errorMessage = name 10 | if (msg) errorMessage += ': ' + msg 11 | stack = errorMessage + stack.slice(stack.indexOf('\n')) 12 | return stack 13 | } 14 | -------------------------------------------------------------------------------- /lib/v8-style.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // returns true if the Error object returns a stack string 4 | // with the signature of 'ErrorType: message' 5 | module.exports = function v8StyleStackMessage() { 6 | var e = new Error('yep') 7 | if (!e.stack) return false 8 | return e.stack.substr(0, 11) === 'Error: yep\n' 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom-error", 3 | "version": "0.2.1", 4 | "author": "Andre Z. Sanchez", 5 | "description": "Create custom errors that inherit Error", 6 | "main": "index.js", 7 | "dependencies": {}, 8 | "devDependencies": { 9 | "rewire": "^2.1.3", 10 | "tap-spec": "~0.2.0", 11 | "tape": "~2.13.3" 12 | }, 13 | "engines": { 14 | "node": ">=0.10.0" 15 | }, 16 | "scripts": { 17 | "test": "tape test/*.js | tap-spec" 18 | }, 19 | "keywords": [ 20 | "error", 21 | "inherit", 22 | "prototype" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/andrezsanchez/custom-error.git" 27 | }, 28 | "homepage": "https://github.com/andrezsanchez/custom-error", 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /test/catch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('catch', function(t) { 7 | t.plan(2) 8 | 9 | var MyError = error('MyError') 10 | 11 | try { 12 | throw MyError('faffy') 13 | } 14 | catch (err) { 15 | t.equals(err.name, 'MyError', 'should set error name correctly') 16 | t.equals(err.message, 'faffy', 'should set error message correctly') 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /test/inheritance.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('inheritance', function(t) { 7 | t.plan(7) 8 | 9 | var MyError = error('MyError') 10 | MyError.prototype.code = 77 11 | 12 | t.ok(MyError() instanceof Error, 'MyError instances should inherit Error') 13 | t.ok(MyError() instanceof MyError, 'MyError instances should inherit MyError') 14 | t.equals(MyError().code, 77, 'MyError instances should inherit MyError.prototype properties') 15 | 16 | 17 | var OtherError = error('OtherError', MyError) 18 | 19 | t.ok(OtherError() instanceof Error, 'OtherError instances should inherit Error') 20 | t.ok(OtherError() instanceof MyError, 'OtherError instances should inherit MyError') 21 | t.ok(OtherError() instanceof OtherError, 'OtherError instances should inherit OtherError') 22 | 23 | t.equals(OtherError().code, 77, 'OtherError instances should inherit MyError.prototype properties') 24 | }) 25 | -------------------------------------------------------------------------------- /test/inspect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('inspect', function(t) { 7 | t.plan(2) 8 | 9 | var MyError = error('MyError') 10 | 11 | var blank = new MyError() 12 | 13 | t.equals(blank.inspect(), '[MyError]', 'errors should have a proper inspect value') 14 | 15 | var msg = new MyError('wat') 16 | t.equals(msg.inspect(), '[MyError: wat]', 'errors should have a proper inspect value') 17 | }) 18 | -------------------------------------------------------------------------------- /test/message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('message', function(t) { 7 | t.plan(2) 8 | var MyError = error('MyError') 9 | t.equals(MyError().name, 'MyError', 'name should be correct') 10 | t.equals(MyError('rawr').message, 'rawr', 'message should be correct') 11 | }) 12 | -------------------------------------------------------------------------------- /test/new.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('new', function(t) { 7 | t.plan(2) 8 | var MyError = error('MyError') 9 | t.ok(new MyError() instanceof MyError, 'new MyError() should create an instance of MyError') 10 | t.ok(MyError() instanceof MyError, 'MyError() should create an instance of MyError') 11 | }) 12 | -------------------------------------------------------------------------------- /test/reformat-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var reformat = require('../lib/reformat') 5 | 6 | test('reformat', function(t) { 7 | t.plan(1) 8 | 9 | var error = [ 10 | 'Error: bad stuff happened', 11 | ' at rad:1337:42', 12 | ' at hopesAndDreams:1:1' 13 | ].join('\n') 14 | 15 | var shouldEqual = [ 16 | 'OtherError: rad stuff happened', 17 | ' at rad:1337:42', 18 | ' at hopesAndDreams:1:1' 19 | ].join('\n') 20 | 21 | t.equal( 22 | reformat(error, 'OtherError', 'rad stuff happened'), 23 | shouldEqual, 24 | 'should return true for v8 style Error stack traces' 25 | ) 26 | }) 27 | -------------------------------------------------------------------------------- /test/throw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var error = require('../') 5 | 6 | test('throw', function(t) { 7 | t.plan(2) 8 | 9 | var MyError = error('MyError') 10 | 11 | var f 12 | f = function() { 13 | throw MyError() 14 | } 15 | t.throws(f, MyError, 'Should throw MyError') 16 | 17 | f = function() { 18 | throw new MyError() 19 | } 20 | t.throws(f, MyError, 'Should throw MyError') 21 | }) 22 | -------------------------------------------------------------------------------- /test/v8-style-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape') 4 | var rewire = require('rewire') 5 | 6 | test('v8-style errors', function(t) { 7 | var v8StyleErrors 8 | t.plan(2) 9 | 10 | var FakeV8Error = function(msg) { 11 | this.stack = 'Error: ' + msg +'\n' 12 | } 13 | v8StyleErrors = rewire('../lib/v8-style') 14 | v8StyleErrors.__set__('Error', FakeV8Error) 15 | 16 | t.true(v8StyleErrors(), 'should return true for v8 style Error stack traces') 17 | 18 | var FakeFirefoxError = function(msg) { 19 | this.stack = 'func@http://localhost:8081/index.js:2:11\n' 20 | } 21 | 22 | v8StyleErrors = rewire('../lib/v8-style') 23 | v8StyleErrors.__set__('Error', FakeFirefoxError) 24 | 25 | t.false(v8StyleErrors(), 'should return false for other styles of Error stack traces') 26 | }) 27 | --------------------------------------------------------------------------------