├── .gitignore ├── .gitmodules ├── .npmignore ├── examples ├── varargs.js ├── fullStack.js ├── nested.js ├── multierror.js ├── verror.js ├── werror.js ├── info.js ├── levels-werror.js └── levels-verror.js ├── CONTRIBUTING.md ├── package.json ├── CHANGES.md ├── Makefile ├── test ├── common.js ├── tst.context.js ├── tst.info.js ├── tst.findcause.js ├── tst.multierror.js ├── tst.verror.js ├── tst.inherit.js ├── tst.werror.js └── tst.common.js ├── LICENSE ├── experiments └── test-error-properties.js ├── jsl.node.conf ├── Makefile.targ ├── lib └── verror.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/catest"] 2 | path = deps/catest 3 | url = https://github.com/joyent/catest.git 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .gitmodules 3 | deps 4 | examples 5 | experiments 6 | jsl.node.conf 7 | Makefile 8 | Makefile.targ 9 | test 10 | -------------------------------------------------------------------------------- /examples/varargs.js: -------------------------------------------------------------------------------- 1 | var VError = require('../lib/verror'); 2 | 3 | var opname = 'read'; 4 | var err = new VError('"%s" operation failed', opname); 5 | console.log(err.message); 6 | console.log(err.stack); 7 | -------------------------------------------------------------------------------- /examples/fullStack.js: -------------------------------------------------------------------------------- 1 | var VError = require('../lib/verror'); 2 | 3 | var err1 = new VError('something bad happened'); 4 | /* ... */ 5 | var err2 = new VError(err1, 'something really bad happened here'); 6 | 7 | console.log(VError.fullStack(err2)); 8 | -------------------------------------------------------------------------------- /examples/nested.js: -------------------------------------------------------------------------------- 1 | var VError = require('../lib/verror'); 2 | var err1 = new Error('No such file or directory'); 3 | var err2 = new VError(err1, 'failed to stat "%s"', '/junk'); 4 | var err3 = new VError(err2, 'request failed'); 5 | console.error(err3.message); 6 | -------------------------------------------------------------------------------- /examples/multierror.js: -------------------------------------------------------------------------------- 1 | var MultiError = require('../lib/verror').MultiError; 2 | 3 | var err = new MultiError([ 4 | new Error('failed to resolve DNS name "abc.example.com"'), 5 | new Error('failed to resolve DNS name "def.example.com"') 6 | ]); 7 | console.error(err.message); 8 | -------------------------------------------------------------------------------- /examples/verror.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var VError = require('../lib/verror'); 3 | 4 | var filename = '/nonexistent'; 5 | fs.stat(filename, function (err1) { 6 | var err2 = new VError(err1, 'stat "%s" failed', filename); 7 | console.error(err2.message); 8 | console.error(err2.cause().message); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/werror.js: -------------------------------------------------------------------------------- 1 | var mod_fs = require('fs'); 2 | var mod_verror = require('../lib/verror'); 3 | 4 | var filename = '/nonexistent'; 5 | 6 | mod_fs.stat(filename, function (err1) { 7 | var err2 = new mod_verror.WError(err1, 'failed to stat "%s"', filename); 8 | 9 | /* The following would normally be higher up the stack. */ 10 | var err3 = new mod_verror.WError(err2, 'failed to handle request'); 11 | console.log(err3.message); 12 | console.log(err3.toString()); 13 | console.log(err3.stack); 14 | }); 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository uses GitHub pull requests for code review. 4 | 5 | See the [Joyent Engineering 6 | Guidelines](https://github.com/joyent/eng/blob/master/docs/index.md) for general 7 | best practices expected in this repository. 8 | 9 | Contributions should be "make prepush" clean. The "prepush" target runs the 10 | "check" target, which requires these separate tools: 11 | 12 | * https://github.com/joyent/jsstyle 13 | * https://github.com/joyent/javascriptlint 14 | 15 | If you're changing something non-trivial or user-facing, you may want to submit 16 | an issue first. 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "verror", 3 | "version": "1.10.1", 4 | "description": "richer JavaScript errors", 5 | "main": "./lib/verror.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/joyent/node-verror.git" 9 | }, 10 | "dependencies": { 11 | "assert-plus": "^1.0.0", 12 | "core-util-is": "1.0.2", 13 | "extsprintf": "^1.2.0" 14 | }, 15 | "engines": { 16 | "node": ">=0.6.0" 17 | }, 18 | "scripts": { 19 | "test": "make test" 20 | }, 21 | "license": "MIT", 22 | "keywords": [ 23 | "error", 24 | "errors", 25 | "err", 26 | "exception", 27 | "exceptions", 28 | "custom" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Not yet released 4 | 5 | * #65 tst.multierror.js failure on Node v8 and later 6 | 7 | ## v1.10.0 8 | 9 | * #49 want convenience functions for MultiErrors 10 | 11 | ## v1.9.0 12 | 13 | * #47 could use VError.hasCauseWithName() 14 | 15 | ## v1.8.1 16 | 17 | * #39 captureStackTrace lost when inheriting from WError 18 | 19 | ## v1.8.0 20 | 21 | * #23 Preserve original stack trace(s) 22 | 23 | ## v1.7.0 24 | 25 | * #10 better support for extra properties on Errors 26 | * #11 make it easy to find causes of a particular kind 27 | * #29 No documentation on how to Install this package 28 | * #36 elide development-only files from npm package 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2016, Joyent, Inc. All rights reserved. 3 | # 4 | # Makefile: top-level Makefile 5 | # 6 | # This Makefile contains only repo-specific logic and uses included makefiles 7 | # to supply common targets (javascriptlint, jsstyle, restdown, etc.), which are 8 | # used by other repos as well. 9 | # 10 | 11 | # 12 | # Tools 13 | # 14 | CATEST = deps/catest/catest 15 | NPM = npm 16 | 17 | # 18 | # Files 19 | # 20 | JS_FILES := $(shell find lib examples test -name '*.js') 21 | JSL_FILES_NODE = $(JS_FILES) 22 | JSSTYLE_FILES = $(JS_FILES) 23 | JSL_CONF_NODE = jsl.node.conf 24 | 25 | .PHONY: all 26 | all: 27 | $(NPM) install 28 | 29 | .PHONY: test 30 | test: $(CATEST) 31 | $(CATEST) -a 32 | 33 | $(CATEST): deps/catest/.git 34 | 35 | include ./Makefile.targ 36 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * test/common.js: common utility functions used in multiple tests 3 | */ 4 | 5 | exports.cleanStack = cleanStack; 6 | exports.oldNode = oldNode; 7 | 8 | /* 9 | * Remove full paths and relative line numbers from stack traces so that we can 10 | * compare against "known-good" output. 11 | */ 12 | function cleanStack(stacktxt) 13 | { 14 | var re = new RegExp('\\(/.*/tst.*js:\\d+:\\d+\\)', 'gm'); 15 | stacktxt = stacktxt.replace(re, '(dummy filename)'); 16 | return (stacktxt); 17 | } 18 | 19 | /* 20 | * Node's behavior with respect to Error's names and messages changed 21 | * significantly with v0.12, so a number of tests regrettably need to check for 22 | * that. 23 | */ 24 | function oldNode() 25 | { 26 | return (/^0\.10\./.test(process.versions['node'])); 27 | } 28 | -------------------------------------------------------------------------------- /examples/info.js: -------------------------------------------------------------------------------- 1 | var VError = require('../lib/verror'); 2 | 3 | var err1 = new VError('something bad happened'); 4 | /* ... */ 5 | var err2 = new VError({ 6 | 'name': 'ConnectionError', 7 | 'cause': err1, 8 | 'info': { 9 | 'errno': 'ECONNREFUSED', 10 | 'remote_ip': '127.0.0.1', 11 | 'port': 215 12 | } 13 | }, 'failed to connect to "%s:%d"', '127.0.0.1', 215); 14 | 15 | console.log(err2.message); 16 | console.log(err2.name); 17 | console.log(VError.info(err2)); 18 | console.log(err2.stack); 19 | 20 | var err3 = new VError({ 21 | 'name': 'RequestError', 22 | 'cause': err2, 23 | 'info': { 24 | 'errno': 'EBADREQUEST' 25 | } 26 | }, 'request failed'); 27 | 28 | console.log(err3.message); 29 | console.log(err3.name); 30 | console.log(VError.info(err3)); 31 | console.log(err3.stack); 32 | -------------------------------------------------------------------------------- /examples/levels-werror.js: -------------------------------------------------------------------------------- 1 | var extsprintf = require('extsprintf'); 2 | var fs = require('fs'); 3 | var VError = require('../lib/verror'); 4 | 5 | function checkFile(filename, callback) { 6 | fs.stat(filename, function (err) { 7 | if (err) 8 | /* Annotate the "stat" error with what we were doing. */ 9 | return (callback(new VError(err, 10 | 'failed to check "%s"', filename))); 11 | 12 | /* ... */ 13 | return (callback()); 14 | }); 15 | } 16 | 17 | function handleRequest(filename, callback) { 18 | checkFile('/nonexistent', function (err) { 19 | if (err) 20 | /* Wrap the "checkFile" error. */ 21 | return (callback(new VError.WError( 22 | err, 'request failed'))); 23 | 24 | /* ... */ 25 | return (callback()); 26 | }); 27 | } 28 | 29 | handleRequest('/nonexistent', function (err) { 30 | if (err) { 31 | console.log(err.message); 32 | console.log(err.toString()); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /examples/levels-verror.js: -------------------------------------------------------------------------------- 1 | var extsprintf = require('extsprintf'); 2 | var fs = require('fs'); 3 | var VError = require('../lib/verror'); 4 | 5 | function checkFile(filename, callback) { 6 | fs.stat(filename, function (err) { 7 | if (err) 8 | /* Annotate the "stat" error with what we were doing. */ 9 | return (callback(new VError(err, 10 | 'failed to check "%s"', filename))); 11 | 12 | /* ... */ 13 | return (callback()); 14 | }); 15 | } 16 | 17 | function handleRequest(filename, callback) { 18 | checkFile('/nonexistent', function (err) { 19 | if (err) 20 | /* Annotate the "checkFile" error. */ 21 | return (callback(new VError( 22 | err, 'request failed'))); 23 | 24 | /* ... */ 25 | return (callback()); 26 | }); 27 | } 28 | 29 | handleRequest('/nonexistent', function (err) { 30 | if (err) { 31 | console.log(err.message); 32 | console.log(extsprintf.sprintf('%r', err)); 33 | } 34 | 35 | /* ... */ 36 | }); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Joyent, Inc. All rights reserved. 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/tst.context.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.context.js: tests that cause works with errors from different contexts. 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | var mod_verror = require('../lib/verror'); 7 | var mod_vm = require('vm'); 8 | 9 | var VError = mod_verror.VError; 10 | var WError = mod_verror.WError; 11 | 12 | var prog1 = 'callback(new Error(), "Error")'; 13 | var prog2 = 'var e = new Error(); e.name = "BarError"; callback(e, "BarError")'; 14 | 15 | function runTests(cerr, name) { 16 | var verr; 17 | 18 | /* 19 | * The constructor should recognize the other context's Error as an 20 | * error for wrapping, and not as an options object. 21 | */ 22 | verr = new VError(cerr); 23 | mod_assert.equal(verr.cause(), cerr); 24 | 25 | verr = new VError({ cause: cerr }); 26 | mod_assert.equal(verr.cause(), cerr); 27 | 28 | /* 29 | * The assertions done at each step while walking the cause chain 30 | * should be okay with the other context's Error. 31 | */ 32 | mod_assert.deepEqual( 33 | mod_verror.findCauseByName(cerr, 'FooError'), null); 34 | mod_assert.equal( 35 | mod_verror.findCauseByName(verr, name), cerr); 36 | 37 | /* 38 | * Verify that functions that take an Error as an argument 39 | * accept the Error created in the other context. 40 | */ 41 | mod_assert.deepEqual(mod_verror.cause(cerr), null); 42 | mod_assert.deepEqual(mod_verror.info(cerr), {}); 43 | mod_assert.equal(typeof (mod_verror.fullStack(cerr)), 'string'); 44 | } 45 | 46 | var context = mod_vm.createContext({ 47 | 'callback': runTests 48 | }); 49 | 50 | /* 51 | * We run the same set of tests using two different errors: one whose name is 52 | * the default "Error", and one whose name has been changed. 53 | * 54 | * Note that changing the name is not the same as having a constructor that 55 | * inherits from Error. Such Errors are not currently supported when 56 | * constructed in another context. 57 | */ 58 | mod_vm.runInContext(prog1, context); 59 | mod_vm.runInContext(prog2, context); 60 | -------------------------------------------------------------------------------- /test/tst.info.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.info.js: tests the way informational properties are inherited with nested 3 | * errors. 4 | */ 5 | 6 | var mod_assert = require('assert'); 7 | var mod_fs = require('fs'); 8 | var mod_verror = require('../lib/verror'); 9 | 10 | var VError = mod_verror.VError; 11 | 12 | var err1, err2, err3; 13 | 14 | /* base case using "options" to specify cause */ 15 | err1 = new Error('bad'); 16 | err2 = new VError({ 17 | 'cause': err1 18 | }, 'worse'); 19 | mod_assert.equal(err2.cause(), err1); 20 | mod_assert.equal(err2.message, 'worse: bad'); 21 | mod_assert.deepEqual(VError.info(err2), {}); 22 | 23 | /* simple info usage */ 24 | err1 = new VError({ 25 | 'name': 'MyError', 26 | 'info': { 27 | 'errno': 'EDEADLK', 28 | 'anobject': { 'hello': 'world' } 29 | } 30 | }, 'bad'); 31 | mod_assert.equal(err1.name, 'MyError'); 32 | mod_assert.deepEqual(VError.info(err1), { 33 | 'errno': 'EDEADLK', 34 | 'anobject': { 'hello': 'world' } 35 | }); 36 | 37 | /* simple property propagation using old syntax */ 38 | err2 = new VError(err1, 'worse'); 39 | mod_assert.equal(err2.cause(), err1); 40 | mod_assert.equal(err2.message, 'worse: bad'); 41 | mod_assert.deepEqual(VError.info(err2), { 42 | 'errno': 'EDEADLK', 43 | 'anobject': { 'hello': 'world' } 44 | }); 45 | 46 | /* one property override */ 47 | err2 = new VError({ 48 | 'cause': err1, 49 | 'info': { 50 | 'anobject': { 'hello': 'moon' } 51 | } 52 | }, 'worse'); 53 | mod_assert.equal(err2.cause(), err1); 54 | mod_assert.equal(err2.message, 'worse: bad'); 55 | mod_assert.deepEqual(VError.info(err2), { 56 | 'errno': 'EDEADLK', 57 | 'anobject': { 'hello': 'moon' } 58 | }); 59 | 60 | /* add a third-level to the chain */ 61 | err3 = new VError({ 62 | 'cause': err2, 63 | 'name': 'BigError', 64 | 'info': { 65 | 'remote_ip': '127.0.0.1' 66 | } 67 | }, 'what next'); 68 | mod_assert.equal(err3.name, 'BigError'); 69 | mod_assert.equal(VError.info(err3).remote_ip, '127.0.0.1'); 70 | mod_assert.equal(err3.cause(), err2); 71 | mod_assert.equal(err3.message, 'what next: worse: bad'); 72 | mod_assert.equal(VError.info(err3).errno, 'EDEADLK'); 73 | mod_assert.deepEqual(VError.info(err3).anobject, { 'hello': 'moon' }); 74 | 75 | console.log('test passed'); 76 | -------------------------------------------------------------------------------- /experiments/test-error-properties.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This program attempts to determine what's possible with Node.js Error 3 | * objects, particularly around the properties we'd like to have: 4 | * 5 | * - "name" property can be set and read 6 | * - "name" property appears correct in toString() 7 | * - "message" property can be set and read 8 | * - "message" property appears correct in toString() 9 | * - instanceof Error is true 10 | * - ... even for second- and third-level subclasses 11 | * - instanceof is true for each parent class 12 | * - "stack" exists 13 | * - "stack" is a lazy property 14 | * - "stack" is correct (shows the right stack trace) 15 | * - other properties can be added 16 | * 17 | * In both v0.10.28 and v0.11.14-pre, the "err.name" and "err.message" 18 | * properties can be set both before and after fetching "err.stack", and their 19 | * values are always correct in err.toString(). 20 | * 21 | * In v0.10.28, the values of "err.name" and "err.message" as of the *first* 22 | * time "err.stack" is accessed are immortalized in the "err.stack" value. As a 23 | * result, if you set "err.name" and "err.message" in subclass constructors, we 24 | * should be fine. 25 | * 26 | * In v0.11.14-pre, the initial values of "err.name" and "err.message" are 27 | * immortalized in the "err.stack" value, so if these are changed after 28 | * construction, "err.stack" will always refer to the constructor name and 29 | * initial message. This is unfortunate, but probably also not a big deal. 30 | */ 31 | var VError = require('../lib/verror'); 32 | 33 | var errorname = 'OtherName'; 34 | var message = 'my sample error'; 35 | var omessage = 'other error message'; 36 | 37 | function main() 38 | { 39 | console.log('node %s', process.version); 40 | runBattery(Error); 41 | console.log('==============================='); 42 | runBattery(VError); 43 | } 44 | 45 | function runBattery(klass) 46 | { 47 | var name, error; 48 | 49 | name = klass.name; 50 | 51 | error = new klass(message); 52 | printErrorInfo(name, 'simple', error); 53 | 54 | error.name = errorname; 55 | printErrorInfo(name, 'changed "name" after fetching stack', error); 56 | 57 | error = new klass(message); 58 | error.name = errorname; 59 | printErrorInfo(name, 'changed "name" before fetching stack', error); 60 | 61 | error = new klass(message); 62 | error.message = omessage; 63 | printErrorInfo(name, 'changed "message" before fetching stack', error); 64 | 65 | error.message = message; 66 | printErrorInfo(name, 'changed "message" back after fetching stack', 67 | error); 68 | 69 | error = new klass(message); 70 | error.otherprop = 'otherpropvalue'; 71 | printErrorInfo(name, 'with "otherprop" property', error); 72 | } 73 | 74 | function printErrorInfo(classname, label, err) 75 | { 76 | console.log('------------------'); 77 | console.log('test: %s, %s', classname, label); 78 | console.log('instanceof Error: %s', err instanceof Error); 79 | console.log('constructor: %s', err.constructor.name); 80 | console.log('error name: %s', err.name); 81 | console.log('error message: %s', err.message); 82 | console.log('error toString: %s', err.toString()); 83 | console.log('has "otherprop": %s', err.hasOwnProperty('otherprop')); 84 | console.log('error stack: %s', err.stack); 85 | console.log('inspect: ', err); 86 | } 87 | 88 | main(); 89 | -------------------------------------------------------------------------------- /test/tst.findcause.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.findcause.js: tests findCauseByName()/hasCauseWithName(). 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | var mod_util = require('util'); 7 | var mod_verror = require('../lib/verror'); 8 | 9 | var SError = mod_verror.SError; 10 | var VError = mod_verror.VError; 11 | var WError = mod_verror.WError; 12 | 13 | var findCauseByName = VError.findCauseByName; 14 | var hasCauseWithName = VError.hasCauseWithName; 15 | 16 | /* 17 | * This class deliberately doesn't inherit from our error classes. 18 | */ 19 | function MyError() 20 | { 21 | Error.call(this, 'here is my error'); 22 | } 23 | 24 | mod_util.inherits(MyError, Error); 25 | MyError.prototype.name = 'MyError'; 26 | 27 | 28 | function main() 29 | { 30 | /* 31 | * We'll build up a cause chain using each of our classes and make sure 32 | * that findCauseByName() traverses all the way to the bottom. This 33 | * ends up testing that findCauseByName() works with each of these 34 | * classes. 35 | */ 36 | var err1, err2, err3, err4; 37 | 38 | err1 = new MyError(); 39 | err2 = new VError({ 40 | 'name': 'ErrorTwo', 41 | 'cause': err1 42 | }, 'basic verror (number two)'); 43 | err3 = new SError({ 44 | 'name': 'ErrorThree', 45 | 'cause': err2 46 | }, 'strict error (number three)'); 47 | err4 = new WError({ 48 | 'name': 'ErrorFour', 49 | 'cause': err3 50 | }, 'werror (number four)'); 51 | 52 | /* 53 | * Our top-level error should have all of the causes in its chain. 54 | */ 55 | mod_assert.strictEqual(err4, findCauseByName(err4, 'ErrorFour')); 56 | mod_assert.strictEqual(true, hasCauseWithName(err4, 'ErrorFour')); 57 | mod_assert.strictEqual(err3, findCauseByName(err4, 'ErrorThree')); 58 | mod_assert.strictEqual(true, hasCauseWithName(err4, 'ErrorThree')); 59 | mod_assert.strictEqual(err2, findCauseByName(err4, 'ErrorTwo')); 60 | mod_assert.strictEqual(true, hasCauseWithName(err4, 'ErrorTwo')); 61 | mod_assert.strictEqual(err1, findCauseByName(err4, 'MyError')); 62 | mod_assert.strictEqual(true, hasCauseWithName(err4, 'MyError')); 63 | 64 | /* 65 | * By contrast, the next-level errors should have only their own causes. 66 | */ 67 | mod_assert.strictEqual(null, findCauseByName(err3, 'ErrorFour')); 68 | mod_assert.strictEqual(false, hasCauseWithName(err3, 'ErrorFour')); 69 | mod_assert.strictEqual(err3, findCauseByName(err3, 'ErrorThree')); 70 | mod_assert.strictEqual(true, hasCauseWithName(err3, 'ErrorThree')); 71 | mod_assert.strictEqual(err2, findCauseByName(err3, 'ErrorTwo')); 72 | mod_assert.strictEqual(true, hasCauseWithName(err3, 'ErrorTwo')); 73 | mod_assert.strictEqual(err1, findCauseByName(err3, 'MyError')); 74 | mod_assert.strictEqual(true, hasCauseWithName(err3, 'MyError')); 75 | 76 | mod_assert.strictEqual(null, findCauseByName(err2, 'ErrorFour')); 77 | mod_assert.strictEqual(false, hasCauseWithName(err2, 'ErrorFour')); 78 | mod_assert.strictEqual(null, findCauseByName(err2, 'ErrorThree')); 79 | mod_assert.strictEqual(false, hasCauseWithName(err2, 'ErrorThree')); 80 | mod_assert.strictEqual(err2, findCauseByName(err2, 'ErrorTwo')); 81 | mod_assert.strictEqual(true, hasCauseWithName(err2, 'ErrorTwo')); 82 | mod_assert.strictEqual(err1, findCauseByName(err2, 'MyError')); 83 | mod_assert.strictEqual(true, hasCauseWithName(err2, 'MyError')); 84 | 85 | /* 86 | * These functions must work on non-VError errors. 87 | */ 88 | mod_assert.strictEqual(err1, findCauseByName(err1, 'MyError')); 89 | mod_assert.strictEqual(true, hasCauseWithName(err1, 'MyError')); 90 | mod_assert.strictEqual(null, findCauseByName(err1, 'ErrorTwo')); 91 | mod_assert.strictEqual(false, hasCauseWithName(err1, 'ErrorTwo')); 92 | 93 | err1 = new Error('a very basic error'); 94 | mod_assert.strictEqual(err1, findCauseByName(err1, 'Error')); 95 | mod_assert.strictEqual(true, hasCauseWithName(err1, 'Error')); 96 | mod_assert.strictEqual(null, findCauseByName(err1, 'MyError')); 97 | mod_assert.strictEqual(false, hasCauseWithName(err1, 'MyError')); 98 | 99 | /* 100 | * These functions should throw an Error when given bad argument types. 101 | */ 102 | mod_assert.throws(function () { findCauseByName(null, 'AnError'); }, 103 | /err must be an Error/); 104 | mod_assert.throws(function () { hasCauseWithName(null, 'AnError'); }, 105 | /err must be an Error/); 106 | mod_assert.throws(function () { findCauseByName(err1, null); }, 107 | /string.*is required/); 108 | mod_assert.throws(function () { hasCauseWithName(err1, null); }, 109 | /string.*is required/); 110 | 111 | console.error('test passed'); 112 | } 113 | 114 | main(); 115 | -------------------------------------------------------------------------------- /test/tst.multierror.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.multierror.js: tests MultiError class 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | var mod_verror = require('../lib/verror'); 7 | var mod_testcommon = require('./common'); 8 | 9 | var MultiError = mod_verror.MultiError; 10 | var errorFromList = mod_verror.errorFromList; 11 | var errorForEach = mod_verror.errorForEach; 12 | 13 | /* 14 | * Save the generic parts of all stack traces so we can avoid hardcoding 15 | * Node-specific implementation details in our testing of stack traces. 16 | * The stack trace limit has to be large enough to capture all of Node's frames, 17 | * which are more than the default (10 frames) in Node v6.x. 18 | */ 19 | Error.stackTraceLimit = 20; 20 | var nodestack = new Error().stack.split('\n').slice(2).join('\n'); 21 | 22 | function main() 23 | { 24 | var err1, err2, err3, merr, stack; 25 | var accum, doAccum; 26 | 27 | mod_assert.throws(function () { 28 | console.error(new MultiError()); 29 | }, /list of errors \(array\) is required/); 30 | 31 | mod_assert.throws(function () { 32 | console.error(new MultiError([])); 33 | }, /must be at least one error/); 34 | 35 | err1 = new Error('error one'); 36 | err2 = new Error('error two'); 37 | err3 = new Error('error three'); 38 | merr = new MultiError([ err1, err2, err3 ]); 39 | mod_assert.equal(err1, merr.cause()); 40 | mod_assert.equal(merr.message, 'first of 3 errors: error one'); 41 | mod_assert.equal(merr.name, 'MultiError'); 42 | stack = mod_testcommon.cleanStack(merr.stack); 43 | mod_assert.equal(stack, [ 44 | 'MultiError: first of 3 errors: error one', 45 | ' at main (dummy filename)', 46 | ' at Object. (dummy filename)' 47 | ].join('\n') + '\n' + nodestack); 48 | 49 | merr = new MultiError([ err1 ]); 50 | mod_assert.equal(merr.message, 'first of 1 error: error one'); 51 | mod_assert.equal(merr.name, 'MultiError'); 52 | stack = mod_testcommon.cleanStack(merr.stack); 53 | mod_assert.equal(stack, [ 54 | 'MultiError: first of 1 error: error one', 55 | ' at main (dummy filename)', 56 | ' at Object. (dummy filename)' 57 | ].join('\n') + '\n' + nodestack); 58 | 59 | 60 | /* errorFromList */ 61 | mod_assert.throws(function () { 62 | console.error(errorFromList()); 63 | }, /^AssertionError.*: errors \(\[object\]\) is required$/); 64 | 65 | mod_assert.throws(function () { 66 | console.error(errorFromList(null)); 67 | }, /^AssertionError.*: errors \(\[object\]\) is required$/); 68 | 69 | mod_assert.throws(function () { 70 | console.error(errorFromList({})); 71 | }, /^AssertionError.*: errors \(\[object\]\) is required$/); 72 | 73 | mod_assert.throws(function () { 74 | console.error(errorFromList('asdf')); 75 | }, /^AssertionError.*: errors \(\[object\]\) is required$/); 76 | 77 | mod_assert.throws(function () { 78 | console.error(errorFromList([ new Error(), 17 ])); 79 | }, /^AssertionError.*: errors \(\[object\]\) is required$/); 80 | 81 | mod_assert.throws(function () { 82 | console.error(errorFromList([ new Error(), {} ])); 83 | }, /^AssertionError/); 84 | 85 | mod_assert.strictEqual(null, errorFromList([])); 86 | mod_assert.ok(err1 == errorFromList([ err1 ])); 87 | mod_assert.ok(err2 == errorFromList([ err2 ])); 88 | merr = errorFromList([ err1, err2, err3 ]); 89 | mod_assert.ok(merr instanceof MultiError); 90 | mod_assert.ok(merr.errors()[0] == err1); 91 | mod_assert.ok(merr.errors()[1] == err2); 92 | mod_assert.ok(merr.errors()[2] == err3); 93 | 94 | 95 | /* errorForEach */ 96 | mod_assert.throws(function () { 97 | console.error(errorForEach()); 98 | }, /^AssertionError.*: err must be an Error$/); 99 | 100 | mod_assert.throws(function () { 101 | console.error(errorForEach(null)); 102 | }, /^AssertionError.*: err must be an Error$/); 103 | 104 | mod_assert.throws(function () { 105 | console.error(errorForEach(err1)); 106 | }, /^AssertionError.*: func \(func\) is required$/); 107 | 108 | mod_assert.throws(function () { 109 | console.error(errorForEach(err1, {})); 110 | }, /^AssertionError.*: func \(func\) is required$/); 111 | 112 | mod_assert.throws(function () { 113 | console.error(errorForEach({}, function () {})); 114 | }, /^AssertionError.*: err must be an Error$/); 115 | 116 | accum = []; 117 | doAccum = function (e) { accum.push(e); }; 118 | 119 | accum = []; 120 | errorForEach(err1, doAccum); 121 | mod_assert.equal(accum.length, 1); 122 | mod_assert.ok(accum[0] == err1); 123 | 124 | accum = []; 125 | errorForEach(merr, doAccum); 126 | mod_assert.equal(accum.length, 3); 127 | mod_assert.ok(accum[0] == err1); 128 | mod_assert.ok(accum[1] == err2); 129 | mod_assert.ok(accum[2] == err3); 130 | } 131 | 132 | main(); 133 | -------------------------------------------------------------------------------- /test/tst.verror.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.verror.js: tests functionality that's specific to the VError and SError 3 | * classes. 4 | */ 5 | 6 | var mod_assert = require('assert'); 7 | var mod_verror = require('../lib/verror'); 8 | var mod_testcommon = require('./common'); 9 | 10 | var SError = mod_verror.SError; 11 | var VError = mod_verror.VError; 12 | var WError = mod_verror.WError; 13 | 14 | /* 15 | * Save the generic parts of all stack traces so we can avoid hardcoding 16 | * Node-specific implementation details in our testing of stack traces. 17 | * The stack trace limit has to be large enough to capture all of Node's frames, 18 | * which are more than the default (10 frames) in Node v6.x. 19 | */ 20 | Error.stackTraceLimit = 20; 21 | var nodestack = new Error().stack.split('\n').slice(2).join('\n'); 22 | 23 | function main() 24 | { 25 | var err, suberr, stack, stackname; 26 | 27 | console.error('running VError/SError tests'); 28 | 29 | /* "null" or "undefined" as string for extsprintf */ 30 | err = new VError('my %s string', null); 31 | mod_assert.equal('my null string', err.message); 32 | err = new VError('my %s string', undefined); 33 | mod_assert.equal('my undefined string', err.message); 34 | 35 | mod_assert.throws(function () { 36 | console.error( 37 | new VError({ 'strict': true }, 'my %s string', null)); 38 | }, /attempted to print undefined or null as a string/); 39 | mod_assert.throws(function () { 40 | console.error(new SError('my %s string', undefined)); 41 | }, /attempted to print undefined or null as a string/); 42 | 43 | mod_assert.throws(function () { 44 | console.error(new SError('my %s string', null)); 45 | }, /attempted to print undefined or null as a string/); 46 | mod_assert.throws(function () { 47 | console.error(new SError('my %s string', undefined)); 48 | }, /attempted to print undefined or null as a string/); 49 | 50 | /* caused by another error, with no additional message */ 51 | suberr = new Error('root cause'); 52 | err = new VError(suberr); 53 | mod_assert.equal(err.message, ': root cause'); 54 | mod_assert.ok(err.cause() === suberr); 55 | 56 | err = new VError({ 'cause': suberr }); 57 | mod_assert.equal(err.message, ': root cause'); 58 | mod_assert.ok(err.cause() === suberr); 59 | 60 | /* caused by another error, with annotation */ 61 | err = new VError(suberr, 'proximate cause: %d issues', 3); 62 | mod_assert.equal(err.message, 'proximate cause: 3 issues: root cause'); 63 | mod_assert.ok(err.cause() === suberr); 64 | stack = mod_testcommon.cleanStack(err.stack); 65 | mod_assert.equal(stack, [ 66 | 'VError: proximate cause: 3 issues: root cause', 67 | ' at main (dummy filename)', 68 | ' at Object. (dummy filename)' 69 | ].join('\n') + '\n' + nodestack); 70 | 71 | err = new SError({ 'cause': suberr }, 'proximate cause: %d issues', 3); 72 | mod_assert.equal(err.message, 'proximate cause: 3 issues: root cause'); 73 | mod_assert.ok(err.cause() === suberr); 74 | stack = mod_testcommon.cleanStack(err.stack); 75 | /* See the comment in tst.common.js. */ 76 | stackname = mod_testcommon.oldNode() ? 'SError': 'VError'; 77 | mod_assert.equal(stack, [ 78 | stackname + ': proximate cause: 3 issues: root cause', 79 | ' at main (dummy filename)', 80 | ' at Object. (dummy filename)' 81 | ].join('\n') + '\n' + nodestack); 82 | 83 | /* caused by another VError, with annotation. */ 84 | suberr = err; 85 | err = new VError(suberr, 'top'); 86 | mod_assert.equal(err.message, 87 | 'top: proximate cause: 3 issues: root cause'); 88 | mod_assert.ok(err.cause() === suberr); 89 | 90 | err = new VError({ 'cause': suberr }, 'top'); 91 | mod_assert.equal(err.message, 92 | 'top: proximate cause: 3 issues: root cause'); 93 | mod_assert.ok(err.cause() === suberr); 94 | 95 | /* caused by a WError */ 96 | suberr = new WError(new Error('root cause'), 'mid'); 97 | err = new VError(suberr, 'top'); 98 | mod_assert.equal(err.message, 'top: mid'); 99 | mod_assert.ok(err.cause() === suberr); 100 | 101 | /* fullStack */ 102 | suberr = new VError(new Error('root cause'), 'mid'); 103 | err = new VError(suberr, 'top'); 104 | stack = mod_testcommon.cleanStack(VError.fullStack(err)); 105 | mod_assert.equal(stack, [ 106 | 'VError: top: mid: root cause', 107 | ' at main (dummy filename)', 108 | ' at Object. (dummy filename)' 109 | ].join('\n') + '\n' + nodestack + '\n' + [ 110 | 'caused by: VError: mid: root cause', 111 | ' at main (dummy filename)', 112 | ' at Object. (dummy filename)' 113 | ].join('\n') + '\n' + nodestack + '\n' + [ 114 | 'caused by: Error: root cause', 115 | ' at main (dummy filename)', 116 | ' at Object. (dummy filename)' 117 | ].join('\n') + '\n' + nodestack); 118 | } 119 | 120 | main(); 121 | -------------------------------------------------------------------------------- /test/tst.inherit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.inherit.js: test that inheriting from VError and WError work as expected. 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | var mod_util = require('util'); 7 | var mod_testcommon = require('./common'); 8 | 9 | var VError = require('../lib/verror'); 10 | var WError = VError.WError; 11 | var err, suberr, stack, nodestack; 12 | 13 | function VErrorChild() 14 | { 15 | VError.apply(this, Array.prototype.slice.call(arguments)); 16 | } 17 | 18 | mod_util.inherits(VErrorChild, VError); 19 | VErrorChild.prototype.name = 'VErrorChild'; 20 | 21 | 22 | function WErrorChild() 23 | { 24 | WError.apply(this, Array.prototype.slice.call(arguments)); 25 | } 26 | 27 | mod_util.inherits(WErrorChild, WError); 28 | WErrorChild.prototype.name = 'WErrorChild'; 29 | 30 | /* 31 | * Save the generic parts of all stack traces so we can avoid hardcoding 32 | * Node-specific implementation details in our testing of stack traces. 33 | * The stack trace limit has to be large enough to capture all of Node's frames, 34 | * which are more than the default (10 frames) in Node v6.x. 35 | */ 36 | Error.stackTraceLimit = 20; 37 | nodestack = new Error().stack.split('\n').slice(2).join('\n'); 38 | 39 | suberr = new Error('root cause'); 40 | err = new VErrorChild(suberr, 'top'); 41 | mod_assert.ok(err instanceof Error); 42 | mod_assert.ok(err instanceof VError); 43 | mod_assert.ok(err instanceof VErrorChild); 44 | mod_assert.equal(err.cause(), suberr); 45 | mod_assert.equal(err.message, 'top: root cause'); 46 | mod_assert.equal(err.toString(), 'VErrorChild: top: root cause'); 47 | stack = mod_testcommon.cleanStack(err.stack); 48 | mod_assert.equal(stack, [ 49 | 'VErrorChild: top: root cause', 50 | ' at Object. (dummy filename)', 51 | nodestack 52 | ].join('\n')); 53 | 54 | suberr = new Error('root cause'); 55 | err = new WErrorChild(suberr, 'top'); 56 | mod_assert.ok(err instanceof Error); 57 | mod_assert.ok(err instanceof WError); 58 | mod_assert.ok(err instanceof WErrorChild); 59 | mod_assert.equal(err.cause(), suberr); 60 | mod_assert.equal(err.message, 'top'); 61 | mod_assert.equal(err.toString(), 62 | 'WErrorChild: top; caused by Error: root cause'); 63 | stack = mod_testcommon.cleanStack(err.stack); 64 | 65 | /* 66 | * On Node 0.10 and earlier, the 'stack' property appears to use the error's 67 | * toString() method. On newer versions, it appears to use the message 68 | * property the first time err.stack is accessed (_not_ when it was 69 | * constructed). Since the point of WError is to omit the cause messages from 70 | * the WError's message, there's no way to have the err.stack property show the 71 | * detailed message in Node 0.12 and later. 72 | */ 73 | if (mod_testcommon.oldNode()) { 74 | mod_assert.equal(stack, [ 75 | 'WErrorChild: top; caused by Error: root cause', 76 | ' at Object. (dummy filename)', 77 | nodestack 78 | ].join('\n')); 79 | } else { 80 | mod_assert.equal(stack, [ 81 | 'WErrorChild: top', 82 | ' at Object. (dummy filename)', 83 | nodestack 84 | ].join('\n')); 85 | } 86 | 87 | /* 88 | * Test that ".toString()" uses the constructor name, so that setting 89 | * ".prototype.name" isn't necessary. 90 | */ 91 | function VErrorChildNoName() { 92 | VError.apply(this, Array.prototype.slice.call(arguments)); 93 | } 94 | mod_util.inherits(VErrorChildNoName, VError); 95 | err = new VErrorChildNoName('top'); 96 | mod_assert.equal(err.toString(), 'VErrorChildNoName: top'); 97 | 98 | function WErrorChildNoName() { 99 | WError.apply(this, Array.prototype.slice.call(arguments)); 100 | } 101 | mod_util.inherits(WErrorChildNoName, WError); 102 | err = new WErrorChildNoName('top'); 103 | mod_assert.equal(err.toString(), 'WErrorChildNoName: top'); 104 | 105 | 106 | /* 107 | * Test that `.prototype.name` can be used for the `.toString()` 108 | * when the ctor is anonymous. 109 | */ 110 | var VErrorChildAnon = function () { 111 | VError.apply(this, Array.prototype.slice.call(arguments)); 112 | }; 113 | mod_util.inherits(VErrorChildAnon, VError); 114 | VErrorChildAnon.prototype.name = 'VErrorChildAnon'; 115 | err = new VErrorChildAnon('top'); 116 | mod_assert.equal(err.toString(), 'VErrorChildAnon: top'); 117 | 118 | var WErrorChildAnon = function () { 119 | WError.apply(this, Array.prototype.slice.call(arguments)); 120 | }; 121 | mod_util.inherits(WErrorChildAnon, WError); 122 | WErrorChildAnon.prototype.name = 'WErrorChildAnon'; 123 | err = new WErrorChildAnon('top'); 124 | mod_assert.equal(err.toString(), 'WErrorChildAnon: top'); 125 | 126 | /* 127 | * Test that we get an appropriate exception name in toString() output. 128 | */ 129 | err = new VError('top'); 130 | err.name = 'CustomNameError'; 131 | mod_assert.equal(err.toString(), 'CustomNameError: top'); 132 | 133 | err = new WError('top'); 134 | err.name = 'CustomNameError'; 135 | mod_assert.equal(err.toString(), 'CustomNameError: top'); 136 | -------------------------------------------------------------------------------- /test/tst.werror.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.werror.js: tests basic functionality specific to the WError class. 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | var mod_verror = require('../lib/verror'); 7 | var mod_testcommon = require('./common'); 8 | 9 | var VError = mod_verror.VError; 10 | var WError = mod_verror.WError; 11 | 12 | /* 13 | * Save the generic parts of all stack traces so we can avoid hardcoding 14 | * Node-specific implementation details in our testing of stack traces. 15 | * The stack trace limit has to be large enough to capture all of Node's frames, 16 | * which are more than the default (10 frames) in Node v6.x. 17 | */ 18 | Error.stackTraceLimit = 20; 19 | var nodestack = new Error().stack.split('\n').slice(2).join('\n'); 20 | 21 | function main() 22 | { 23 | var err, suberr, stack, stackmessageTop, stackmessageMid; 24 | 25 | /* 26 | * Most of the test cases here have analogs in tst.common.js. In this 27 | * test, we check for WError-specific behavior (e.g., toString()). 28 | */ 29 | console.error('running WError-specific tests'); 30 | 31 | /* no arguments */ 32 | err = new WError(); 33 | mod_assert.equal(err.toString(), 'WError'); 34 | mod_assert.ok(err.cause() === undefined); 35 | stack = mod_testcommon.cleanStack(err.stack); 36 | mod_assert.equal(stack, [ 37 | 'WError', 38 | ' at main (dummy filename)', 39 | ' at Object. (dummy filename)' 40 | ].join('\n') + '\n' + nodestack); 41 | 42 | /* options-argument form */ 43 | err = new WError({}); 44 | mod_assert.equal(err.toString(), 'WError'); 45 | mod_assert.ok(err.cause() === undefined); 46 | 47 | /* simple message */ 48 | err = new WError('my error'); 49 | mod_assert.equal(err.message, 'my error'); 50 | mod_assert.equal(err.toString(), 'WError: my error'); 51 | mod_assert.ok(err.cause() === undefined); 52 | stack = mod_testcommon.cleanStack(err.stack); 53 | mod_assert.equal(stack, [ 54 | 'WError: my error', 55 | ' at main (dummy filename)', 56 | ' at Object. (dummy filename)' 57 | ].join('\n') + '\n' + nodestack); 58 | 59 | err = new WError({}, 'my error'); 60 | mod_assert.equal(err.toString(), 'WError: my error'); 61 | mod_assert.ok(err.cause() === undefined); 62 | 63 | /* caused by another error, with no additional message */ 64 | suberr = new Error('root cause'); 65 | err = new WError(suberr); 66 | mod_assert.equal(err.message, ''); 67 | mod_assert.equal(err.toString(), 'WError; caused by Error: root cause'); 68 | mod_assert.ok(err.cause() === suberr); 69 | 70 | err = new WError({ 'cause': suberr }); 71 | mod_assert.equal(err.message, ''); 72 | mod_assert.equal(err.toString(), 'WError; caused by Error: root cause'); 73 | mod_assert.ok(err.cause() === suberr); 74 | 75 | /* caused by another error, with annotation */ 76 | err = new WError(suberr, 'proximate cause: %d issues', 3); 77 | mod_assert.equal(err.message, 'proximate cause: 3 issues'); 78 | mod_assert.equal(err.toString(), 'WError: proximate cause: 3 issues; ' + 79 | 'caused by Error: root cause'); 80 | mod_assert.ok(err.cause() === suberr); 81 | stack = mod_testcommon.cleanStack(err.stack); 82 | /* See the comment in tst.inherit.js. */ 83 | stackmessageTop = mod_testcommon.oldNode() ? 84 | 'WError: proximate cause: 3 issues; caused by Error: root cause' : 85 | 'WError: proximate cause: 3 issues'; 86 | mod_assert.equal(stack, [ 87 | stackmessageTop, 88 | ' at main (dummy filename)', 89 | ' at Object. (dummy filename)' 90 | ].join('\n') + '\n' + nodestack); 91 | 92 | err = new WError({ 'cause': suberr }, 'proximate cause: %d issues', 3); 93 | mod_assert.equal(err.message, 'proximate cause: 3 issues'); 94 | mod_assert.equal(err.toString(), 'WError: proximate cause: 3 issues; ' + 95 | 'caused by Error: root cause'); 96 | mod_assert.ok(err.cause() === suberr); 97 | stack = mod_testcommon.cleanStack(err.stack); 98 | mod_assert.equal(stack, [ 99 | stackmessageTop, 100 | ' at main (dummy filename)', 101 | ' at Object. (dummy filename)' 102 | ].join('\n') + '\n' + nodestack); 103 | 104 | /* caused by another WError, with annotation. */ 105 | suberr = err; 106 | err = new WError(suberr, 'top'); 107 | mod_assert.equal(err.message, 'top'); 108 | mod_assert.equal(err.toString(), 'WError: top; caused by WError: ' + 109 | 'proximate cause: 3 issues; caused by Error: root cause'); 110 | mod_assert.ok(err.cause() === suberr); 111 | 112 | err = new WError({ 'cause': suberr }, 'top'); 113 | mod_assert.equal(err.message, 'top'); 114 | mod_assert.equal(err.toString(), 'WError: top; caused by WError: ' + 115 | 'proximate cause: 3 issues; caused by Error: root cause'); 116 | mod_assert.ok(err.cause() === suberr); 117 | 118 | /* caused by a VError */ 119 | suberr = new VError(new Error('root cause'), 'mid'); 120 | err = new WError(suberr, 'top'); 121 | mod_assert.equal(err.message, 'top'); 122 | mod_assert.equal(err.toString(), 123 | 'WError: top; caused by VError: mid: root cause'); 124 | mod_assert.ok(err.cause() === suberr); 125 | 126 | /* fullStack */ 127 | suberr = new WError(new Error('root cause'), 'mid'); 128 | err = new WError(suberr, 'top'); 129 | stack = mod_testcommon.cleanStack(VError.fullStack(err)); 130 | /* See the comment in tst.inherit.js. */ 131 | stackmessageMid = mod_testcommon.oldNode() ? 132 | 'WError: mid; caused by Error: root cause' : 133 | 'WError: mid'; 134 | stackmessageTop = mod_testcommon.oldNode() ? 135 | 'WError: top; caused by ' + stackmessageMid : 136 | 'WError: top'; 137 | mod_assert.equal(stack, [ 138 | stackmessageTop, 139 | ' at main (dummy filename)', 140 | ' at Object. (dummy filename)' 141 | ].join('\n') + '\n' + nodestack + '\n' + [ 142 | 'caused by: ' + stackmessageMid, 143 | ' at main (dummy filename)', 144 | ' at Object. (dummy filename)' 145 | ].join('\n') + '\n' + nodestack + '\n' + [ 146 | 'caused by: Error: root cause', 147 | ' at main (dummy filename)', 148 | ' at Object. (dummy filename)' 149 | ].join('\n') + '\n' + nodestack); 150 | } 151 | 152 | main(); 153 | -------------------------------------------------------------------------------- /test/tst.common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tst.common.js: tests functionality that's common to the VError, SError, and 3 | * WError classes. 4 | */ 5 | 6 | var mod_assert = require('assert'); 7 | var mod_verror = require('../lib/verror'); 8 | var mod_testcommon = require('./common'); 9 | 10 | var SError = mod_verror.SError; 11 | var VError = mod_verror.VError; 12 | var WError = mod_verror.WError; 13 | 14 | /* 15 | * Save the generic parts of all stack traces so we can avoid hardcoding 16 | * Node-specific implementation details in our testing of stack traces. 17 | * The stack trace limit has to be large enough to capture all of Node's frames, 18 | * which are more than the default (10 frames) in Node v6.x. 19 | */ 20 | Error.stackTraceLimit = 20; 21 | var nodestack = new Error().stack.split('\n').slice(2).join('\n'); 22 | 23 | /* 24 | * Runs all tests using the class "cons". We'll apply this to each of the main 25 | * classes. 26 | */ 27 | function runTests(cons, label) 28 | { 29 | var err, stack, stackname; 30 | 31 | console.error('running common tests for: %s', cons.name); 32 | 33 | /* 34 | * On Node v0.10 and earlier, the name that's used in the "stack" output 35 | * is the constructor that was used for this object. On Node v0.12 and 36 | * later, it's the value of the "name" property on the Error when it was 37 | * constructed. 38 | */ 39 | if (mod_testcommon.oldNode()) { 40 | stackname = cons.name; 41 | } else { 42 | stackname = label; 43 | } 44 | 45 | /* no arguments */ 46 | err = new cons(); 47 | mod_assert.equal(err.name, label); 48 | mod_assert.ok(err instanceof Error); 49 | mod_assert.ok(err instanceof cons); 50 | mod_assert.equal(err.message, ''); 51 | mod_assert.ok(err.cause() === undefined); 52 | stack = mod_testcommon.cleanStack(err.stack); 53 | mod_assert.equal(stack, [ 54 | stackname, 55 | ' at runTests (dummy filename)', 56 | ' at Object. (dummy filename)' 57 | ].join('\n') + '\n' + nodestack); 58 | 59 | /* used without "new" */ 60 | err = cons('test %s', 'foo'); 61 | mod_assert.equal(err.name, label); 62 | mod_assert.ok(err instanceof Error); 63 | mod_assert.ok(err instanceof cons); 64 | mod_assert.equal(err.message, 'test foo'); 65 | 66 | /* options-argument form */ 67 | err = new cons({}); 68 | mod_assert.equal(err.name, label); 69 | mod_assert.equal(err.message, ''); 70 | mod_assert.ok(err.cause() === undefined); 71 | 72 | /* simple message */ 73 | err = new cons('my error'); 74 | mod_assert.equal(err.name, label); 75 | mod_assert.equal(err.message, 'my error'); 76 | mod_assert.ok(err.cause() === undefined); 77 | stack = mod_testcommon.cleanStack(err.stack); 78 | mod_assert.equal(stack, [ 79 | stackname + ': my error', 80 | ' at runTests (dummy filename)', 81 | ' at Object. (dummy filename)' 82 | ].join('\n') + '\n' + nodestack); 83 | 84 | err = new cons({}, 'my error'); 85 | mod_assert.equal(err.name, label); 86 | mod_assert.equal(err.message, 'my error'); 87 | mod_assert.ok(err.cause() === undefined); 88 | 89 | /* fullStack */ 90 | err = new cons('Some error'); 91 | stack = mod_testcommon.cleanStack(VError.fullStack(err)); 92 | mod_assert.equal(stack, [ 93 | stackname + ': Some error', 94 | ' at runTests (dummy filename)', 95 | ' at Object. (dummy filename)' 96 | ].join('\n') + '\n' + nodestack); 97 | 98 | err = new Error('Some error'); 99 | stack = mod_testcommon.cleanStack(VError.fullStack(err)); 100 | mod_assert.equal(stack, [ 101 | 'Error: Some error', 102 | ' at runTests (dummy filename)', 103 | ' at Object. (dummy filename)' 104 | ].join('\n') + '\n' + nodestack); 105 | 106 | /* printf-style message */ 107 | err = new cons('%s error: %3d problems', 'very bad', 15); 108 | mod_assert.equal(err.message, 'very bad error: 15 problems'); 109 | mod_assert.ok(err.cause() === undefined); 110 | 111 | err = new cons({}, '%s error: %3d problems', 'very bad', 15); 112 | mod_assert.equal(err.message, 'very bad error: 15 problems'); 113 | mod_assert.ok(err.cause() === undefined); 114 | 115 | /* null cause (for backwards compatibility with older versions) */ 116 | err = new cons(null, 'my error'); 117 | mod_assert.equal(err.message, 'my error'); 118 | mod_assert.ok(err.cause() === undefined); 119 | stack = mod_testcommon.cleanStack(err.stack); 120 | mod_assert.equal(stack, [ 121 | stackname + ': my error', 122 | ' at runTests (dummy filename)', 123 | ' at Object. (dummy filename)' 124 | ].join('\n') + '\n' + nodestack); 125 | 126 | err = new cons({ 'cause': null }, 'my error'); 127 | mod_assert.equal(err.message, 'my error'); 128 | mod_assert.ok(err.cause() === undefined); 129 | 130 | err = new cons(null); 131 | mod_assert.equal(err.message, ''); 132 | mod_assert.ok(err.cause() === undefined); 133 | stack = mod_testcommon.cleanStack(err.stack); 134 | mod_assert.equal(stack, [ 135 | stackname, 136 | ' at runTests (dummy filename)', 137 | ' at Object. (dummy filename)' 138 | ].join('\n') + '\n' + nodestack); 139 | 140 | /* constructorOpt */ 141 | function makeErr(options) { 142 | return (new cons(options, 'test error')); 143 | } 144 | err = makeErr({}); 145 | stack = mod_testcommon.cleanStack(err.stack); 146 | mod_assert.equal(stack, [ 147 | stackname + ': test error', 148 | ' at makeErr (dummy filename)', 149 | ' at runTests (dummy filename)', 150 | ' at Object. (dummy filename)' 151 | ].join('\n') + '\n' + nodestack); 152 | 153 | err = makeErr({ 'constructorOpt': makeErr }); 154 | stack = mod_testcommon.cleanStack(err.stack); 155 | mod_assert.equal(stack, [ 156 | stackname + ': test error', 157 | ' at runTests (dummy filename)', 158 | ' at Object. (dummy filename)' 159 | ].join('\n') + '\n' + nodestack); 160 | 161 | /* invoked without "new" */ 162 | err = cons('my %s string', 'testing!'); 163 | mod_assert.equal(err.name, label); 164 | mod_assert.ok(err instanceof cons); 165 | mod_assert.ok(err instanceof Error); 166 | mod_assert.equal(err.message, 'my testing! string'); 167 | 168 | /* custom "name" */ 169 | err = new cons({ 'name': 'SomeOtherError' }, 'another kind of error'); 170 | mod_assert.equal(err.name, 'SomeOtherError'); 171 | mod_assert.ok(err instanceof cons); 172 | mod_assert.ok(err instanceof Error); 173 | mod_assert.equal(err.message, 'another kind of error'); 174 | stack = mod_testcommon.cleanStack(err.stack); 175 | mod_assert.equal(stack, [ 176 | 'SomeOtherError: another kind of error', 177 | ' at runTests (dummy filename)', 178 | ' at Object. (dummy filename)' 179 | ].join('\n') + '\n' + nodestack); 180 | } 181 | 182 | runTests(VError, 'VError'); 183 | runTests(WError, 'WError'); 184 | runTests(SError, 'VError'); 185 | -------------------------------------------------------------------------------- /jsl.node.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration File for JavaScript Lint 3 | # 4 | # This configuration file can be used to lint a collection of scripts, or to enable 5 | # or disable warnings for scripts that are linted via the command line. 6 | # 7 | 8 | ### Warnings 9 | # Enable or disable warnings based on requirements. 10 | # Use "+WarningName" to display or "-WarningName" to suppress. 11 | # 12 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent 13 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 14 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 15 | +anon_no_return_value # anonymous function does not always return value 16 | +assign_to_function_call # assignment to a function call 17 | -block_without_braces # block statement without curly braces 18 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 19 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 20 | +default_not_at_end # the default case is not at the end of the switch statement 21 | +dup_option_explicit # duplicate "option explicit" control comment 22 | +duplicate_case_in_switch # duplicate case in switch statement 23 | +duplicate_formal # duplicate formal argument {name} 24 | +empty_statement # empty statement or extra semicolon 25 | +identifier_hides_another # identifer {name} hides an identifier in a parent scope 26 | -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 27 | +incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version. 28 | +invalid_fallthru # unexpected "fallthru" control comment 29 | +invalid_pass # unexpected "pass" control comment 30 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 31 | +leading_decimal_point # leading decimal point may indicate a number or an object member 32 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 33 | +meaningless_block # meaningless block; curly braces have no impact 34 | +mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence 35 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 36 | +missing_break # missing break statement 37 | +missing_break_for_last_case # missing break statement for last case in switch 38 | +missing_default_case # missing default case in switch statement 39 | +missing_option_explicit # the "option explicit" control comment is missing 40 | +missing_semicolon # missing semicolon 41 | +missing_semicolon_for_lambda # missing semicolon for lambda assignment 42 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 43 | +nested_comment # nested comment 44 | +no_return_value # function {name} does not always return a value 45 | +octal_number # leading zeros make an octal number 46 | +parseint_missing_radix # parseInt missing radix parameter 47 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 48 | +redeclared_var # redeclaration of {name} 49 | +trailing_comma_in_array # extra comma is not recommended in array initializers 50 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 51 | +undeclared_identifier # undeclared identifier: {name} 52 | +unreachable_code # unreachable code 53 | -unreferenced_argument # argument declared but never referenced: {name} 54 | -unreferenced_function # function is declared but never referenced: {name} 55 | +unreferenced_variable # variable is declared but never referenced: {name} 56 | +unsupported_version # JavaScript {version} is not supported 57 | +use_of_label # use of label 58 | +useless_assign # useless assignment 59 | +useless_comparison # useless comparison; comparing identical expressions 60 | -useless_quotes # the quotation marks are unnecessary 61 | +useless_void # use of the void type may be unnecessary (void is always undefined) 62 | +var_hides_arg # variable {name} hides argument 63 | +want_assign_or_call # expected an assignment or function call 64 | +with_statement # with statement hides undeclared variables; use temporary variable instead 65 | 66 | 67 | ### Output format 68 | # Customize the format of the error message. 69 | # __FILE__ indicates current file path 70 | # __FILENAME__ indicates current file name 71 | # __LINE__ indicates current line 72 | # __COL__ indicates current column 73 | # __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__) 74 | # __ERROR_NAME__ indicates error name (used in configuration file) 75 | # __ERROR_PREFIX__ indicates error prefix 76 | # __ERROR_MSG__ indicates error message 77 | # 78 | # For machine-friendly output, the output format can be prefixed with 79 | # "encode:". If specified, all items will be encoded with C-slashes. 80 | # 81 | # Visual Studio syntax (default): 82 | +output-format __FILE__(__LINE__): __ERROR__ 83 | # Alternative syntax: 84 | #+output-format __FILE__:__LINE__: __ERROR__ 85 | 86 | 87 | ### Context 88 | # Show the in-line position of the error. 89 | # Use "+context" to display or "-context" to suppress. 90 | # 91 | +context 92 | 93 | 94 | ### Control Comments 95 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 96 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 97 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 98 | # although legacy control comments are enabled by default for backward compatibility. 99 | # 100 | -legacy_control_comments 101 | 102 | 103 | ### Defining identifiers 104 | # By default, "option explicit" is enabled on a per-file basis. 105 | # To enable this for all files, use "+always_use_option_explicit" 106 | -always_use_option_explicit 107 | 108 | # Define certain identifiers of which the lint is not aware. 109 | # (Use this in conjunction with the "undeclared identifier" warning.) 110 | # 111 | # Common uses for webpages might be: 112 | +define __dirname 113 | +define clearInterval 114 | +define clearTimeout 115 | +define console 116 | +define exports 117 | +define global 118 | +define module 119 | +define process 120 | +define require 121 | +define setInterval 122 | +define setTimeout 123 | +define Buffer 124 | +define JSON 125 | +define Math 126 | +define __dirname 127 | +define __filename 128 | 129 | ### JavaScript Version 130 | # To change the default JavaScript version: 131 | #+default-type text/javascript;version=1.5 132 | #+default-type text/javascript;e4x=1 133 | 134 | ### Files 135 | # Specify which files to lint 136 | # Use "+recurse" to enable recursion (disabled by default). 137 | # To add a set of files, use "+process FileName", "+process Folder\Path\*.js", 138 | # or "+process Folder\Path\*.htm". 139 | # 140 | 141 | -------------------------------------------------------------------------------- /Makefile.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.targ: common targets. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This Makefile defines several useful targets and rules. You can use it by 21 | # including it from a Makefile that specifies some of the variables below. 22 | # 23 | # Targets defined in this Makefile: 24 | # 25 | # check Checks JavaScript files for lint and style 26 | # Checks bash scripts for syntax 27 | # Checks SMF manifests for validity against the SMF DTD 28 | # 29 | # clean Removes built files 30 | # 31 | # docs Builds restdown documentation in docs/ 32 | # 33 | # prepush Depends on "check" and "test" 34 | # 35 | # test Does nothing (you should override this) 36 | # 37 | # xref Generates cscope (source cross-reference index) 38 | # 39 | # For details on what these targets are supposed to do, see the Joyent 40 | # Engineering Guide. 41 | # 42 | # To make use of these targets, you'll need to set some of these variables. Any 43 | # variables left unset will simply not be used. 44 | # 45 | # BASH_FILES Bash scripts to check for syntax 46 | # (paths relative to top-level Makefile) 47 | # 48 | # CLEAN_FILES Files to remove as part of the "clean" target. Note 49 | # that files generated by targets in this Makefile are 50 | # automatically included in CLEAN_FILES. These include 51 | # restdown-generated HTML and JSON files. 52 | # 53 | # DOC_FILES Restdown (documentation source) files. These are 54 | # assumed to be contained in "docs/", and must NOT 55 | # contain the "docs/" prefix. 56 | # 57 | # JSL_CONF_NODE Specify JavaScriptLint configuration files 58 | # JSL_CONF_WEB (paths relative to top-level Makefile) 59 | # 60 | # Node.js and Web configuration files are separate 61 | # because you'll usually want different global variable 62 | # configurations. If no file is specified, none is given 63 | # to jsl, which causes it to use a default configuration, 64 | # which probably isn't what you want. 65 | # 66 | # JSL_FILES_NODE JavaScript files to check with Node config file. 67 | # JSL_FILES_WEB JavaScript files to check with Web config file. 68 | # 69 | # JSON_FILES JSON files to be validated 70 | # 71 | # JSSTYLE_FILES JavaScript files to be style-checked 72 | # 73 | # You can also override these variables: 74 | # 75 | # BASH Path to bash (default: "bash") 76 | # 77 | # CSCOPE_DIRS Directories to search for source files for the cscope 78 | # index. (default: ".") 79 | # 80 | # JSL Path to JavaScriptLint (default: "jsl") 81 | # 82 | # JSL_FLAGS_NODE Additional flags to pass through to JSL 83 | # JSL_FLAGS_WEB 84 | # JSL_FLAGS 85 | # 86 | # JSON Path to json tool (default: "json") 87 | # 88 | # JSSTYLE Path to jsstyle (default: "jsstyle") 89 | # 90 | # JSSTYLE_FLAGS Additional flags to pass through to jsstyle 91 | # 92 | # RESTDOWN_EXT By default '.restdown' is required for DOC_FILES 93 | # (see above). If you want to use, say, '.md' instead, then 94 | # set 'RESTDOWN_EXT=.md' in your Makefile. 95 | # 96 | 97 | # 98 | # Defaults for the various tools we use. 99 | # 100 | BASH ?= bash 101 | BASHSTYLE ?= tools/bashstyle 102 | CP ?= cp 103 | CSCOPE ?= cscope 104 | CSCOPE_DIRS ?= . 105 | JSL ?= jsl 106 | JSON ?= json 107 | JSSTYLE ?= jsstyle 108 | MKDIR ?= mkdir -p 109 | MV ?= mv 110 | RESTDOWN_FLAGS ?= 111 | RESTDOWN_EXT ?= .restdown 112 | RMTREE ?= rm -rf 113 | JSL_FLAGS ?= --nologo --nosummary 114 | 115 | ifeq ($(shell uname -s),SunOS) 116 | TAR ?= gtar 117 | else 118 | TAR ?= tar 119 | endif 120 | 121 | 122 | # 123 | # Defaults for other fixed values. 124 | # 125 | BUILD = build 126 | DISTCLEAN_FILES += $(BUILD) 127 | DOC_BUILD = $(BUILD)/docs/public 128 | 129 | # 130 | # Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}. 131 | # 132 | ifneq ($(origin JSL_CONF_NODE), undefined) 133 | JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE) 134 | endif 135 | 136 | ifneq ($(origin JSL_CONF_WEB), undefined) 137 | JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB) 138 | endif 139 | 140 | # 141 | # Targets. For descriptions on what these are supposed to do, see the 142 | # Joyent Engineering Guide. 143 | # 144 | 145 | # 146 | # Instruct make to keep around temporary files. We have rules below that 147 | # automatically update git submodules as needed, but they employ a deps/*/.git 148 | # temporary file. Without this directive, make tries to remove these .git 149 | # directories after the build has completed. 150 | # 151 | .SECONDARY: $($(wildcard deps/*):%=%/.git) 152 | 153 | # 154 | # This rule enables other rules that use files from a git submodule to have 155 | # those files depend on deps/module/.git and have "make" automatically check 156 | # out the submodule as needed. 157 | # 158 | deps/%/.git: 159 | git submodule update --init deps/$* 160 | 161 | # 162 | # These recipes make heavy use of dynamically-created phony targets. The parent 163 | # Makefile defines a list of input files like BASH_FILES. We then say that each 164 | # of these files depends on a fake target called filename.bashchk, and then we 165 | # define a pattern rule for those targets that runs bash in check-syntax-only 166 | # mode. This mechanism has the nice properties that if you specify zero files, 167 | # the rule becomes a noop (unlike a single rule to check all bash files, which 168 | # would invoke bash with zero files), and you can check individual files from 169 | # the command line with "make filename.bashchk". 170 | # 171 | .PHONY: check-bash 172 | check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle) 173 | 174 | %.bashchk: % 175 | $(BASH) -n $^ 176 | 177 | %.bashstyle: % 178 | $(BASHSTYLE) $^ 179 | 180 | .PHONY: check-json 181 | check-json: $(JSON_FILES:%=%.jsonchk) 182 | 183 | %.jsonchk: % 184 | $(JSON) --validate -f $^ 185 | 186 | # 187 | # The above approach can be slow when there are many files to check because it 188 | # requires that "make" invoke the check tool once for each file, rather than 189 | # passing in several files at once. For the JavaScript check targets, we define 190 | # a variable for the target itself *only if* the list of input files is 191 | # non-empty. This avoids invoking the tool if there are no files to check. 192 | # 193 | JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node) 194 | .PHONY: check-jsl-node 195 | check-jsl-node: $(JSL_EXEC) 196 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE) 197 | 198 | JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web) 199 | .PHONY: check-jsl-web 200 | check-jsl-web: $(JSL_EXEC) 201 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB) 202 | 203 | .PHONY: check-jsl 204 | check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET) 205 | 206 | JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle) 207 | .PHONY: check-jsstyle 208 | check-jsstyle: $(JSSTYLE_EXEC) 209 | $(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES) 210 | 211 | .PHONY: check 212 | check: check-jsl check-json $(JSSTYLE_TARGET) check-bash 213 | @echo check ok 214 | 215 | .PHONY: clean 216 | clean:: 217 | -$(RMTREE) $(CLEAN_FILES) 218 | 219 | .PHONY: distclean 220 | distclean:: clean 221 | -$(RMTREE) $(DISTCLEAN_FILES) 222 | 223 | CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out 224 | CLEAN_FILES += $(CSCOPE_FILES) 225 | 226 | .PHONY: xref 227 | xref: cscope.files 228 | $(CSCOPE) -bqR 229 | 230 | .PHONY: cscope.files 231 | cscope.files: 232 | find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \ 233 | -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@ 234 | 235 | # 236 | # The "docs" target is complicated because we do several things here: 237 | # 238 | # (1) Use restdown to build HTML and JSON files from each of DOC_FILES. 239 | # 240 | # (2) Copy these files into $(DOC_BUILD) (build/docs/public), which 241 | # functions as a complete copy of the documentation that could be 242 | # mirrored or served over HTTP. 243 | # 244 | # (3) Then copy any directories and media from docs/media into 245 | # $(DOC_BUILD)/media. This allows projects to include their own media, 246 | # including files that will override same-named files provided by 247 | # restdown. 248 | # 249 | # Step (3) is the surprisingly complex part: in order to do this, we need to 250 | # identify the subdirectories in docs/media, recreate them in 251 | # $(DOC_BUILD)/media, then do the same with the files. 252 | # 253 | DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$") 254 | DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%) 255 | DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%) 256 | 257 | DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null) 258 | DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%) 259 | DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%) 260 | 261 | # 262 | # Like the other targets, "docs" just depends on the final files we want to 263 | # create in $(DOC_BUILD), leveraging other targets and recipes to define how 264 | # to get there. 265 | # 266 | .PHONY: docs 267 | docs: \ 268 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \ 269 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \ 270 | $(DOC_MEDIA_FILES_BUILD) 271 | 272 | # 273 | # We keep the intermediate files so that the next build can see whether the 274 | # files in DOC_BUILD are up to date. 275 | # 276 | .PRECIOUS: \ 277 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 278 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json) 279 | 280 | # 281 | # We do clean those intermediate files, as well as all of DOC_BUILD. 282 | # 283 | CLEAN_FILES += \ 284 | $(DOC_BUILD) \ 285 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 286 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json) 287 | 288 | # 289 | # Before installing the files, we must make sure the directories exist. The | 290 | # syntax tells make that the dependency need only exist, not be up to date. 291 | # Otherwise, it might try to rebuild spuriously because the directory itself 292 | # appears out of date. 293 | # 294 | $(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD) 295 | 296 | $(DOC_BUILD)/%: docs/% | $(DOC_BUILD) 297 | $(CP) $< $@ 298 | 299 | docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC) 300 | $(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $< 301 | 302 | $(DOC_BUILD): 303 | $(MKDIR) $@ 304 | 305 | $(DOC_MEDIA_DIRS_BUILD): 306 | $(MKDIR) $@ 307 | 308 | # 309 | # The default "test" target does nothing. This should usually be overridden by 310 | # the parent Makefile. It's included here so we can define "prepush" without 311 | # requiring the repo to define "test". 312 | # 313 | .PHONY: test 314 | test: 315 | 316 | .PHONY: prepush 317 | prepush: check test 318 | -------------------------------------------------------------------------------- /lib/verror.js: -------------------------------------------------------------------------------- 1 | /* 2 | * verror.js: richer JavaScript errors 3 | */ 4 | 5 | var mod_assertplus = require('assert-plus'); 6 | var mod_util = require('util'); 7 | 8 | var mod_extsprintf = require('extsprintf'); 9 | var mod_isError = require('core-util-is').isError; 10 | var sprintf = mod_extsprintf.sprintf; 11 | 12 | /* 13 | * Public interface 14 | */ 15 | 16 | /* So you can 'var VError = require('verror')' */ 17 | module.exports = VError; 18 | /* For compatibility */ 19 | VError.VError = VError; 20 | /* Other exported classes */ 21 | VError.SError = SError; 22 | VError.WError = WError; 23 | VError.MultiError = MultiError; 24 | 25 | /* 26 | * Common function used to parse constructor arguments for VError, WError, and 27 | * SError. Named arguments to this function: 28 | * 29 | * strict force strict interpretation of sprintf arguments, even 30 | * if the options in "argv" don't say so 31 | * 32 | * argv error's constructor arguments, which are to be 33 | * interpreted as described in README.md. For quick 34 | * reference, "argv" has one of the following forms: 35 | * 36 | * [ sprintf_args... ] (argv[0] is a string) 37 | * [ cause, sprintf_args... ] (argv[0] is an Error) 38 | * [ options, sprintf_args... ] (argv[0] is an object) 39 | * 40 | * This function normalizes these forms, producing an object with the following 41 | * properties: 42 | * 43 | * options equivalent to "options" in third form. This will never 44 | * be a direct reference to what the caller passed in 45 | * (i.e., it may be a shallow copy), so it can be freely 46 | * modified. 47 | * 48 | * shortmessage result of sprintf(sprintf_args), taking options.strict 49 | * into account as described in README.md. 50 | */ 51 | function parseConstructorArguments(args) 52 | { 53 | var argv, options, sprintf_args, shortmessage, k; 54 | 55 | mod_assertplus.object(args, 'args'); 56 | mod_assertplus.bool(args.strict, 'args.strict'); 57 | mod_assertplus.array(args.argv, 'args.argv'); 58 | argv = args.argv; 59 | 60 | /* 61 | * First, figure out which form of invocation we've been given. 62 | */ 63 | if (argv.length === 0) { 64 | options = {}; 65 | sprintf_args = []; 66 | } else if (mod_isError(argv[0])) { 67 | options = { 'cause': argv[0] }; 68 | sprintf_args = argv.slice(1); 69 | } else if (typeof (argv[0]) === 'object') { 70 | options = {}; 71 | for (k in argv[0]) { 72 | options[k] = argv[0][k]; 73 | } 74 | sprintf_args = argv.slice(1); 75 | } else { 76 | mod_assertplus.string(argv[0], 77 | 'first argument to VError, SError, or WError ' + 78 | 'constructor must be a string, object, or Error'); 79 | options = {}; 80 | sprintf_args = argv; 81 | } 82 | 83 | /* 84 | * Now construct the error's message. 85 | * 86 | * extsprintf (which we invoke here with our caller's arguments in order 87 | * to construct this Error's message) is strict in its interpretation of 88 | * values to be processed by the "%s" specifier. The value passed to 89 | * extsprintf must actually be a string or something convertible to a 90 | * String using .toString(). Passing other values (notably "null" and 91 | * "undefined") is considered a programmer error. The assumption is 92 | * that if you actually want to print the string "null" or "undefined", 93 | * then that's easy to do that when you're calling extsprintf; on the 94 | * other hand, if you did NOT want that (i.e., there's actually a bug 95 | * where the program assumes some variable is non-null and tries to 96 | * print it, which might happen when constructing a packet or file in 97 | * some specific format), then it's better to stop immediately than 98 | * produce bogus output. 99 | * 100 | * However, sometimes the bug is only in the code calling VError, and a 101 | * programmer might prefer to have the error message contain "null" or 102 | * "undefined" rather than have the bug in the error path crash the 103 | * program (making the first bug harder to identify). For that reason, 104 | * by default VError converts "null" or "undefined" arguments to their 105 | * string representations and passes those to extsprintf. Programmers 106 | * desiring the strict behavior can use the SError class or pass the 107 | * "strict" option to the VError constructor. 108 | */ 109 | mod_assertplus.object(options); 110 | if (!options.strict && !args.strict) { 111 | sprintf_args = sprintf_args.map(function (a) { 112 | return (a === null ? 'null' : 113 | a === undefined ? 'undefined' : a); 114 | }); 115 | } 116 | 117 | if (sprintf_args.length === 0) { 118 | shortmessage = ''; 119 | } else { 120 | shortmessage = sprintf.apply(null, sprintf_args); 121 | } 122 | 123 | return ({ 124 | 'options': options, 125 | 'shortmessage': shortmessage 126 | }); 127 | } 128 | 129 | /* 130 | * See README.md for reference documentation. 131 | */ 132 | function VError() 133 | { 134 | var args, obj, parsed, cause, ctor, message, k; 135 | 136 | args = Array.prototype.slice.call(arguments, 0); 137 | 138 | /* 139 | * This is a regrettable pattern, but JavaScript's built-in Error class 140 | * is defined to work this way, so we allow the constructor to be called 141 | * without "new". 142 | */ 143 | if (!(this instanceof VError)) { 144 | obj = Object.create(VError.prototype); 145 | VError.apply(obj, arguments); 146 | return (obj); 147 | } 148 | 149 | /* 150 | * For convenience and backwards compatibility, we support several 151 | * different calling forms. Normalize them here. 152 | */ 153 | parsed = parseConstructorArguments({ 154 | 'argv': args, 155 | 'strict': false 156 | }); 157 | 158 | /* 159 | * If we've been given a name, apply it now. 160 | */ 161 | if (parsed.options.name) { 162 | mod_assertplus.string(parsed.options.name, 163 | 'error\'s "name" must be a string'); 164 | this.name = parsed.options.name; 165 | } 166 | 167 | /* 168 | * For debugging, we keep track of the original short message (attached 169 | * this Error particularly) separately from the complete message (which 170 | * includes the messages of our cause chain). 171 | */ 172 | this.jse_shortmsg = parsed.shortmessage; 173 | message = parsed.shortmessage; 174 | 175 | /* 176 | * If we've been given a cause, record a reference to it and update our 177 | * message appropriately. 178 | */ 179 | cause = parsed.options.cause; 180 | if (cause) { 181 | mod_assertplus.ok(mod_isError(cause), 'cause is not an Error'); 182 | this.jse_cause = cause; 183 | 184 | if (!parsed.options.skipCauseMessage) { 185 | message += ': ' + cause.message; 186 | } 187 | } 188 | 189 | /* 190 | * If we've been given an object with properties, shallow-copy that 191 | * here. We don't want to use a deep copy in case there are non-plain 192 | * objects here, but we don't want to use the original object in case 193 | * the caller modifies it later. 194 | */ 195 | this.jse_info = {}; 196 | if (parsed.options.info) { 197 | for (k in parsed.options.info) { 198 | this.jse_info[k] = parsed.options.info[k]; 199 | } 200 | } 201 | 202 | this.message = message; 203 | Error.call(this, message); 204 | 205 | if (Error.captureStackTrace) { 206 | ctor = parsed.options.constructorOpt || this.constructor; 207 | Error.captureStackTrace(this, ctor); 208 | } 209 | 210 | return (this); 211 | } 212 | 213 | mod_util.inherits(VError, Error); 214 | VError.prototype.name = 'VError'; 215 | 216 | VError.prototype.toString = function ve_toString() 217 | { 218 | var str = (this.hasOwnProperty('name') && this.name || 219 | this.constructor.name || this.constructor.prototype.name); 220 | if (this.message) 221 | str += ': ' + this.message; 222 | 223 | return (str); 224 | }; 225 | 226 | /* 227 | * This method is provided for compatibility. New callers should use 228 | * VError.cause() instead. That method also uses the saner `null` return value 229 | * when there is no cause. 230 | */ 231 | VError.prototype.cause = function ve_cause() 232 | { 233 | var cause = VError.cause(this); 234 | return (cause === null ? undefined : cause); 235 | }; 236 | 237 | /* 238 | * Static methods 239 | * 240 | * These class-level methods are provided so that callers can use them on 241 | * instances of Errors that are not VErrors. New interfaces should be provided 242 | * only using static methods to eliminate the class of programming mistake where 243 | * people fail to check whether the Error object has the corresponding methods. 244 | */ 245 | 246 | VError.cause = function (err) 247 | { 248 | mod_assertplus.ok(mod_isError(err), 'err must be an Error'); 249 | return (mod_isError(err.jse_cause) ? err.jse_cause : null); 250 | }; 251 | 252 | VError.info = function (err) 253 | { 254 | var rv, cause, k; 255 | 256 | mod_assertplus.ok(mod_isError(err), 'err must be an Error'); 257 | cause = VError.cause(err); 258 | if (cause !== null) { 259 | rv = VError.info(cause); 260 | } else { 261 | rv = {}; 262 | } 263 | 264 | if (typeof (err.jse_info) == 'object' && err.jse_info !== null) { 265 | for (k in err.jse_info) { 266 | rv[k] = err.jse_info[k]; 267 | } 268 | } 269 | 270 | return (rv); 271 | }; 272 | 273 | VError.findCauseByName = function (err, name) 274 | { 275 | var cause; 276 | 277 | mod_assertplus.ok(mod_isError(err), 'err must be an Error'); 278 | mod_assertplus.string(name, 'name'); 279 | mod_assertplus.ok(name.length > 0, 'name cannot be empty'); 280 | 281 | for (cause = err; cause !== null; cause = VError.cause(cause)) { 282 | mod_assertplus.ok(mod_isError(cause)); 283 | if (cause.name == name) { 284 | return (cause); 285 | } 286 | } 287 | 288 | return (null); 289 | }; 290 | 291 | VError.hasCauseWithName = function (err, name) 292 | { 293 | return (VError.findCauseByName(err, name) !== null); 294 | }; 295 | 296 | VError.fullStack = function (err) 297 | { 298 | mod_assertplus.ok(mod_isError(err), 'err must be an Error'); 299 | 300 | var cause = VError.cause(err); 301 | 302 | if (cause) { 303 | return (err.stack + '\ncaused by: ' + VError.fullStack(cause)); 304 | } 305 | 306 | return (err.stack); 307 | }; 308 | 309 | VError.errorFromList = function (errors) 310 | { 311 | mod_assertplus.arrayOfObject(errors, 'errors'); 312 | 313 | if (errors.length === 0) { 314 | return (null); 315 | } 316 | 317 | errors.forEach(function (e) { 318 | mod_assertplus.ok(mod_isError(e)); 319 | }); 320 | 321 | if (errors.length == 1) { 322 | return (errors[0]); 323 | } 324 | 325 | return (new MultiError(errors)); 326 | }; 327 | 328 | VError.errorForEach = function (err, func) 329 | { 330 | mod_assertplus.ok(mod_isError(err), 'err must be an Error'); 331 | mod_assertplus.func(func, 'func'); 332 | 333 | if (err instanceof MultiError) { 334 | err.errors().forEach(function iterError(e) { func(e); }); 335 | } else { 336 | func(err); 337 | } 338 | }; 339 | 340 | 341 | /* 342 | * SError is like VError, but stricter about types. You cannot pass "null" or 343 | * "undefined" as string arguments to the formatter. 344 | */ 345 | function SError() 346 | { 347 | var args, obj, parsed, options; 348 | 349 | args = Array.prototype.slice.call(arguments, 0); 350 | if (!(this instanceof SError)) { 351 | obj = Object.create(SError.prototype); 352 | SError.apply(obj, arguments); 353 | return (obj); 354 | } 355 | 356 | parsed = parseConstructorArguments({ 357 | 'argv': args, 358 | 'strict': true 359 | }); 360 | 361 | options = parsed.options; 362 | VError.call(this, options, '%s', parsed.shortmessage); 363 | 364 | return (this); 365 | } 366 | 367 | /* 368 | * We don't bother setting SError.prototype.name because once constructed, 369 | * SErrors are just like VErrors. 370 | */ 371 | mod_util.inherits(SError, VError); 372 | 373 | 374 | /* 375 | * Represents a collection of errors for the purpose of consumers that generally 376 | * only deal with one error. Callers can extract the individual errors 377 | * contained in this object, but may also just treat it as a normal single 378 | * error, in which case a summary message will be printed. 379 | */ 380 | function MultiError(errors) 381 | { 382 | mod_assertplus.array(errors, 'list of errors'); 383 | mod_assertplus.ok(errors.length > 0, 'must be at least one error'); 384 | this.ase_errors = errors; 385 | 386 | VError.call(this, { 387 | 'cause': errors[0] 388 | }, 'first of %d error%s', errors.length, errors.length == 1 ? '' : 's'); 389 | } 390 | 391 | mod_util.inherits(MultiError, VError); 392 | MultiError.prototype.name = 'MultiError'; 393 | 394 | MultiError.prototype.errors = function me_errors() 395 | { 396 | return (this.ase_errors.slice(0)); 397 | }; 398 | 399 | 400 | /* 401 | * See README.md for reference details. 402 | */ 403 | function WError() 404 | { 405 | var args, obj, parsed, options; 406 | 407 | args = Array.prototype.slice.call(arguments, 0); 408 | if (!(this instanceof WError)) { 409 | obj = Object.create(WError.prototype); 410 | WError.apply(obj, args); 411 | return (obj); 412 | } 413 | 414 | parsed = parseConstructorArguments({ 415 | 'argv': args, 416 | 'strict': false 417 | }); 418 | 419 | options = parsed.options; 420 | options['skipCauseMessage'] = true; 421 | VError.call(this, options, '%s', parsed.shortmessage); 422 | 423 | return (this); 424 | } 425 | 426 | mod_util.inherits(WError, VError); 427 | WError.prototype.name = 'WError'; 428 | 429 | WError.prototype.toString = function we_toString() 430 | { 431 | var str = (this.hasOwnProperty('name') && this.name || 432 | this.constructor.name || this.constructor.prototype.name); 433 | if (this.message) 434 | str += ': ' + this.message; 435 | if (this.jse_cause && this.jse_cause.message) 436 | str += '; caused by ' + this.jse_cause.toString(); 437 | 438 | return (str); 439 | }; 440 | 441 | /* 442 | * For purely historical reasons, WError's cause() function allows you to set 443 | * the cause. 444 | */ 445 | WError.prototype.cause = function we_cause(c) 446 | { 447 | if (mod_isError(c)) 448 | this.jse_cause = c; 449 | 450 | return (this.jse_cause); 451 | }; 452 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # verror: rich JavaScript errors 2 | 3 | This module provides several classes in support of Joyent's [Best Practices for 4 | Error Handling in Node.js](http://www.joyent.com/developers/node/design/errors). 5 | If you find any of the behavior here confusing or surprising, check out that 6 | document first. 7 | 8 | The error classes here support: 9 | 10 | * printf-style arguments for the message 11 | * chains of causes 12 | * properties to provide extra information about the error 13 | * creating your own subclasses that support all of these 14 | 15 | The classes here are: 16 | 17 | * **VError**, for chaining errors while preserving each one's error message. 18 | This is useful in servers and command-line utilities when you want to 19 | propagate an error up a call stack, but allow various levels to add their own 20 | context. See examples below. 21 | * **WError**, for wrapping errors while hiding the lower-level messages from the 22 | top-level error. This is useful for API endpoints where you don't want to 23 | expose internal error messages, but you still want to preserve the error chain 24 | for logging and debugging. 25 | * **SError**, which is just like VError but interprets printf-style arguments 26 | more strictly. 27 | * **MultiError**, which is just an Error that encapsulates one or more other 28 | errors. (This is used for parallel operations that return several errors.) 29 | 30 | 31 | # Quick start 32 | 33 | First, install the package: 34 | 35 | npm install verror 36 | 37 | If nothing else, you can use VError as a drop-in replacement for the built-in 38 | JavaScript Error class, with the addition of printf-style messages: 39 | 40 | ```javascript 41 | var err = new VError('missing file: "%s"', '/etc/passwd'); 42 | console.log(err.message); 43 | ``` 44 | 45 | This prints: 46 | 47 | missing file: "/etc/passwd" 48 | 49 | You can also pass a `cause` argument, which is any other Error object: 50 | 51 | ```javascript 52 | var fs = require('fs'); 53 | var filename = '/nonexistent'; 54 | fs.stat(filename, function (err1) { 55 | var err2 = new VError(err1, 'stat "%s"', filename); 56 | console.error(err2.message); 57 | }); 58 | ``` 59 | 60 | This prints out: 61 | 62 | stat "/nonexistent": ENOENT, stat '/nonexistent' 63 | 64 | which resembles how Unix programs typically report errors: 65 | 66 | $ sort /nonexistent 67 | sort: open failed: /nonexistent: No such file or directory 68 | 69 | To match the Unixy feel, when you print out the error, just prepend the 70 | program's name to the VError's `message`. Or just call 71 | [node-cmdutil.fail(your_verror)](https://github.com/joyent/node-cmdutil), which 72 | does this for you. 73 | 74 | You can get the next-level Error using `err.cause()`: 75 | 76 | ```javascript 77 | console.error(err2.cause().message); 78 | ``` 79 | 80 | prints: 81 | 82 | ENOENT, stat '/nonexistent' 83 | 84 | Of course, you can chain these as many times as you want, and it works with any 85 | kind of Error: 86 | 87 | ```javascript 88 | var err1 = new Error('No such file or directory'); 89 | var err2 = new VError(err1, 'failed to stat "%s"', '/junk'); 90 | var err3 = new VError(err2, 'request failed'); 91 | console.error(err3.message); 92 | ``` 93 | 94 | This prints: 95 | 96 | request failed: failed to stat "/junk": No such file or directory 97 | 98 | The idea is that each layer in the stack annotates the error with a description 99 | of what it was doing. The end result is a message that explains what happened 100 | at each level. 101 | 102 | You can also decorate Error objects with additional information so that callers 103 | can not only handle each kind of error differently, but also construct their own 104 | error messages (e.g., to localize them, format them, group them by type, and so 105 | on). See the example below. 106 | 107 | 108 | # Deeper dive 109 | 110 | The two main goals for VError are: 111 | 112 | * **Make it easy to construct clear, complete error messages intended for 113 | people.** Clear error messages greatly improve both user experience and 114 | debuggability, so we wanted to make it easy to build them. That's why the 115 | constructor takes printf-style arguments. 116 | * **Make it easy to construct objects with programmatically-accessible 117 | metadata** (which we call _informational properties_). Instead of just saying 118 | "connection refused while connecting to 192.168.1.2:80", you can add 119 | properties like `"ip": "192.168.1.2"` and `"tcpPort": 80`. This can be used 120 | for feeding into monitoring systems, analyzing large numbers of Errors (as 121 | from a log file), or localizing error messages. 122 | 123 | To really make this useful, it also needs to be easy to compose Errors: 124 | higher-level code should be able to augment the Errors reported by lower-level 125 | code to provide a more complete description of what happened. Instead of saying 126 | "connection refused", you can say "operation X failed: connection refused". 127 | That's why VError supports `causes`. 128 | 129 | In order for all this to work, programmers need to know that it's generally safe 130 | to wrap lower-level Errors with higher-level ones. If you have existing code 131 | that handles Errors produced by a library, you should be able to wrap those 132 | Errors with a VError to add information without breaking the error handling 133 | code. There are two obvious ways that this could break such consumers: 134 | 135 | * The error's name might change. People typically use `name` to determine what 136 | kind of Error they've got. To ensure compatibility, you can create VErrors 137 | with custom names, but this approach isn't great because it prevents you from 138 | representing complex failures. For this reason, VError provides 139 | `findCauseByName`, which essentially asks: does this Error _or any of its 140 | causes_ have this specific type? If error handling code uses 141 | `findCauseByName`, then subsystems can construct very specific causal chains 142 | for debuggability and still let people handle simple cases easily. There's an 143 | example below. 144 | * The error's properties might change. People often hang additional properties 145 | off of Error objects. If we wrap an existing Error in a new Error, those 146 | properties would be lost unless we copied them. But there are a variety of 147 | both standard and non-standard Error properties that should _not_ be copied in 148 | this way: most obviously `name`, `message`, and `stack`, but also `fileName`, 149 | `lineNumber`, and a few others. Plus, it's useful for some Error subclasses 150 | to have their own private properties -- and there'd be no way to know whether 151 | these should be copied. For these reasons, VError first-classes these 152 | information properties. You have to provide them in the constructor, you can 153 | only fetch them with the `info()` function, and VError takes care of making 154 | sure properties from causes wind up in the `info()` output. 155 | 156 | Let's put this all together with an example from the node-fast RPC library. 157 | node-fast implements a simple RPC protocol for Node programs. There's a server 158 | and client interface, and clients make RPC requests to servers. Let's say the 159 | server fails with an UnauthorizedError with message "user 'bob' is not 160 | authorized". The client wraps all server errors with a FastServerError. The 161 | client also wraps all request errors with a FastRequestError that includes the 162 | name of the RPC call being made. The result of this failed RPC might look like 163 | this: 164 | 165 | name: FastRequestError 166 | message: "request failed: server error: user 'bob' is not authorized" 167 | rpcMsgid: 168 | rpcMethod: GetObject 169 | cause: 170 | name: FastServerError 171 | message: "server error: user 'bob' is not authorized" 172 | cause: 173 | name: UnauthorizedError 174 | message: "user 'bob' is not authorized" 175 | rpcUser: "bob" 176 | 177 | When the caller uses `VError.info()`, the information properties are collapsed 178 | so that it looks like this: 179 | 180 | message: "request failed: server error: user 'bob' is not authorized" 181 | rpcMsgid: 182 | rpcMethod: GetObject 183 | rpcUser: "bob" 184 | 185 | Taking this apart: 186 | 187 | * The error's message is a complete description of the problem. The caller can 188 | report this directly to its caller, which can potentially make its way back to 189 | an end user (if appropriate). It can also be logged. 190 | * The caller can tell that the request failed on the server, rather than as a 191 | result of a client problem (e.g., failure to serialize the request), a 192 | transport problem (e.g., failure to connect to the server), or something else 193 | (e.g., a timeout). They do this using `findCauseByName('FastServerError')` 194 | rather than checking the `name` field directly. 195 | * If the caller logs this error, the logs can be analyzed to aggregate 196 | errors by cause, by RPC method name, by user, or whatever. Or the 197 | error can be correlated with other events for the same rpcMsgid. 198 | * It wasn't very hard for any part of the code to contribute to this Error. 199 | Each part of the stack has just a few lines to provide exactly what it knows, 200 | with very little boilerplate. 201 | 202 | It's not expected that you'd use these complex forms all the time. Despite 203 | supporting the complex case above, you can still just do: 204 | 205 | new VError("my service isn't working"); 206 | 207 | for the simple cases. 208 | 209 | 210 | # Reference: VError, WError, SError 211 | 212 | VError, WError, and SError are convenient drop-in replacements for `Error` that 213 | support printf-style arguments, first-class causes, informational properties, 214 | and other useful features. 215 | 216 | 217 | ## Constructors 218 | 219 | The VError constructor has several forms: 220 | 221 | ```javascript 222 | /* 223 | * This is the most general form. You can specify any supported options 224 | * (including "cause" and "info") this way. 225 | */ 226 | new VError(options, sprintf_args...) 227 | 228 | /* 229 | * This is a useful shorthand when the only option you need is "cause". 230 | */ 231 | new VError(cause, sprintf_args...) 232 | 233 | /* 234 | * This is a useful shorthand when you don't need any options at all. 235 | */ 236 | new VError(sprintf_args...) 237 | ``` 238 | 239 | All of these forms construct a new VError that behaves just like the built-in 240 | JavaScript `Error` class, with some additional methods described below. 241 | 242 | In the first form, `options` is a plain object with any of the following 243 | optional properties: 244 | 245 | Option name | Type | Meaning 246 | ---------------- | ---------------- | ------- 247 | `name` | string | Describes what kind of error this is. This is intended for programmatic use to distinguish between different kinds of errors. Note that in modern versions of Node.js, this name is ignored in the `stack` property value, but callers can still use the `name` property to get at it. 248 | `cause` | any Error object | Indicates that the new error was caused by `cause`. See `cause()` below. If unspecified, the cause will be `null`. 249 | `strict` | boolean | If true, then `null` and `undefined` values in `sprintf_args` are passed through to `sprintf()`. Otherwise, these are replaced with the strings `'null'`, and '`undefined`', respectively. 250 | `constructorOpt` | function | If specified, then the stack trace for this error ends at function `constructorOpt`. Functions called by `constructorOpt` will not show up in the stack. This is useful when this class is subclassed. 251 | `info` | object | Specifies arbitrary informational properties that are available through the `VError.info(err)` static class method. See that method for details. 252 | 253 | The second form is equivalent to using the first form with the specified `cause` 254 | as the error's cause. This form is distinguished from the first form because 255 | the first argument is an Error. 256 | 257 | The third form is equivalent to using the first form with all default option 258 | values. This form is distinguished from the other forms because the first 259 | argument is not an object or an Error. 260 | 261 | The `WError` constructor is used exactly the same way as the `VError` 262 | constructor. The `SError` constructor is also used the same way as the 263 | `VError` constructor except that in all cases, the `strict` property is 264 | overriden to `true. 265 | 266 | 267 | ## Public properties 268 | 269 | `VError`, `WError`, and `SError` all provide the same public properties as 270 | JavaScript's built-in Error objects. 271 | 272 | Property name | Type | Meaning 273 | ------------- | ------ | ------- 274 | `name` | string | Programmatically-usable name of the error. 275 | `message` | string | Human-readable summary of the failure. Programmatically-accessible details are provided through `VError.info(err)` class method. 276 | `stack` | string | Human-readable stack trace where the Error was constructed. 277 | 278 | For all of these classes, the printf-style arguments passed to the constructor 279 | are processed with `sprintf()` to form a message. For `WError`, this becomes 280 | the complete `message` property. For `SError` and `VError`, this message is 281 | prepended to the message of the cause, if any (with a suitable separator), and 282 | the result becomes the `message` property. 283 | 284 | The `stack` property is managed entirely by the underlying JavaScript 285 | implementation. It's generally implemented using a getter function because 286 | constructing the human-readable stack trace is somewhat expensive. 287 | 288 | ## Class methods 289 | 290 | The following methods are defined on the `VError` class and as exported 291 | functions on the `verror` module. They're defined this way rather than using 292 | methods on VError instances so that they can be used on Errors not created with 293 | `VError`. 294 | 295 | ### `VError.cause(err)` 296 | 297 | The `cause()` function returns the next Error in the cause chain for `err`, or 298 | `null` if there is no next error. See the `cause` argument to the constructor. 299 | Errors can have arbitrarily long cause chains. You can walk the `cause` chain 300 | by invoking `VError.cause(err)` on each subsequent return value. If `err` is 301 | not a `VError`, the cause is `null`. 302 | 303 | ### `VError.info(err)` 304 | 305 | Returns an object with all of the extra error information that's been associated 306 | with this Error and all of its causes. These are the properties passed in using 307 | the `info` option to the constructor. Properties not specified in the 308 | constructor for this Error are implicitly inherited from this error's cause. 309 | 310 | These properties are intended to provide programmatically-accessible metadata 311 | about the error. For an error that indicates a failure to resolve a DNS name, 312 | informational properties might include the DNS name to be resolved, or even the 313 | list of resolvers used to resolve it. The values of these properties should 314 | generally be plain objects (i.e., consisting only of null, undefined, numbers, 315 | booleans, strings, and objects and arrays containing only other plain objects). 316 | 317 | ### `VError.fullStack(err)` 318 | 319 | Returns a string containing the full stack trace, with all nested errors recursively 320 | reported as `'caused by:' + err.stack`. 321 | 322 | ### `VError.findCauseByName(err, name)` 323 | 324 | The `findCauseByName()` function traverses the cause chain for `err`, looking 325 | for an error whose `name` property matches the passed in `name` value. If no 326 | match is found, `null` is returned. 327 | 328 | If all you want is to know _whether_ there's a cause (and you don't care what it 329 | is), you can use `VError.hasCauseWithName(err, name)`. 330 | 331 | If a vanilla error or a non-VError error is passed in, then there is no cause 332 | chain to traverse. In this scenario, the function will check the `name` 333 | property of only `err`. 334 | 335 | ### `VError.hasCauseWithName(err, name)` 336 | 337 | Returns true if and only if `VError.findCauseByName(err, name)` would return 338 | a non-null value. This essentially determines whether `err` has any cause in 339 | its cause chain that has name `name`. 340 | 341 | ### `VError.errorFromList(errors)` 342 | 343 | Given an array of Error objects (possibly empty), return a single error 344 | representing the whole collection of errors. If the list has: 345 | 346 | * 0 elements, returns `null` 347 | * 1 element, returns the sole error 348 | * more than 1 element, returns a MultiError referencing the whole list 349 | 350 | This is useful for cases where an operation may produce any number of errors, 351 | and you ultimately want to implement the usual `callback(err)` pattern. You can 352 | accumulate the errors in an array and then invoke 353 | `callback(VError.errorFromList(errors))` when the operation is complete. 354 | 355 | 356 | ### `VError.errorForEach(err, func)` 357 | 358 | Convenience function for iterating an error that may itself be a MultiError. 359 | 360 | In all cases, `err` must be an Error. If `err` is a MultiError, then `func` is 361 | invoked as `func(errorN)` for each of the underlying errors of the MultiError. 362 | If `err` is any other kind of error, `func` is invoked once as `func(err)`. In 363 | all cases, `func` is invoked synchronously. 364 | 365 | This is useful for cases where an operation may produce any number of warnings 366 | that may be encapsulated with a MultiError -- but may not be. 367 | 368 | This function does not iterate an error's cause chain. 369 | 370 | 371 | ## Examples 372 | 373 | The "Demo" section above covers several basic cases. Here's a more advanced 374 | case: 375 | 376 | ```javascript 377 | var err1 = new VError('something bad happened'); 378 | /* ... */ 379 | var err2 = new VError({ 380 | 'name': 'ConnectionError', 381 | 'cause': err1, 382 | 'info': { 383 | 'errno': 'ECONNREFUSED', 384 | 'remote_ip': '127.0.0.1', 385 | 'port': 215 386 | } 387 | }, 'failed to connect to "%s:%d"', '127.0.0.1', 215); 388 | 389 | console.log(err2.message); 390 | console.log(err2.name); 391 | console.log(VError.info(err2)); 392 | console.log(err2.stack); 393 | ``` 394 | 395 | This outputs: 396 | 397 | failed to connect to "127.0.0.1:215": something bad happened 398 | ConnectionError 399 | { errno: 'ECONNREFUSED', remote_ip: '127.0.0.1', port: 215 } 400 | ConnectionError: failed to connect to "127.0.0.1:215": something bad happened 401 | at Object. (/home/dap/node-verror/examples/info.js:5:12) 402 | at Module._compile (module.js:456:26) 403 | at Object.Module._extensions..js (module.js:474:10) 404 | at Module.load (module.js:356:32) 405 | at Function.Module._load (module.js:312:12) 406 | at Function.Module.runMain (module.js:497:10) 407 | at startup (node.js:119:16) 408 | at node.js:935:3 409 | 410 | Information properties are inherited up the cause chain, with values at the top 411 | of the chain overriding same-named values lower in the chain. To continue that 412 | example: 413 | 414 | ```javascript 415 | var err3 = new VError({ 416 | 'name': 'RequestError', 417 | 'cause': err2, 418 | 'info': { 419 | 'errno': 'EBADREQUEST' 420 | } 421 | }, 'request failed'); 422 | 423 | console.log(err3.message); 424 | console.log(err3.name); 425 | console.log(VError.info(err3)); 426 | console.log(err3.stack); 427 | ``` 428 | 429 | This outputs: 430 | 431 | request failed: failed to connect to "127.0.0.1:215": something bad happened 432 | RequestError 433 | { errno: 'EBADREQUEST', remote_ip: '127.0.0.1', port: 215 } 434 | RequestError: request failed: failed to connect to "127.0.0.1:215": something bad happened 435 | at Object. (/home/dap/node-verror/examples/info.js:20:12) 436 | at Module._compile (module.js:456:26) 437 | at Object.Module._extensions..js (module.js:474:10) 438 | at Module.load (module.js:356:32) 439 | at Function.Module._load (module.js:312:12) 440 | at Function.Module.runMain (module.js:497:10) 441 | at startup (node.js:119:16) 442 | at node.js:935:3 443 | 444 | You can also print the complete stack trace of combined `Error`s by using 445 | `VError.fullStack(err).` 446 | 447 | ```javascript 448 | var err1 = new VError('something bad happened'); 449 | /* ... */ 450 | var err2 = new VError(err1, 'something really bad happened here'); 451 | 452 | console.log(VError.fullStack(err2)); 453 | ``` 454 | 455 | This outputs: 456 | 457 | VError: something really bad happened here: something bad happened 458 | at Object. (/home/dap/node-verror/examples/fullStack.js:5:12) 459 | at Module._compile (module.js:409:26) 460 | at Object.Module._extensions..js (module.js:416:10) 461 | at Module.load (module.js:343:32) 462 | at Function.Module._load (module.js:300:12) 463 | at Function.Module.runMain (module.js:441:10) 464 | at startup (node.js:139:18) 465 | at node.js:968:3 466 | caused by: VError: something bad happened 467 | at Object. (/home/dap/node-verror/examples/fullStack.js:3:12) 468 | at Module._compile (module.js:409:26) 469 | at Object.Module._extensions..js (module.js:416:10) 470 | at Module.load (module.js:343:32) 471 | at Function.Module._load (module.js:300:12) 472 | at Function.Module.runMain (module.js:441:10) 473 | at startup (node.js:139:18) 474 | at node.js:968:3 475 | 476 | `VError.fullStack` is also safe to use on regular `Error`s, so feel free to use 477 | it whenever you need to extract the stack trace from an `Error`, regardless if 478 | it's a `VError` or not. 479 | 480 | # Reference: MultiError 481 | 482 | MultiError is an Error class that represents a group of Errors. This is used 483 | when you logically need to provide a single Error, but you want to preserve 484 | information about multiple underlying Errors. A common case is when you execute 485 | several operations in parallel and some of them fail. 486 | 487 | MultiErrors are constructed as: 488 | 489 | ```javascript 490 | new MultiError(error_list) 491 | ``` 492 | 493 | `error_list` is an array of at least one `Error` object. 494 | 495 | The cause of the MultiError is the first error provided. None of the other 496 | `VError` options are supported. The `message` for a MultiError consists the 497 | `message` from the first error, prepended with a message indicating that there 498 | were other errors. 499 | 500 | For example: 501 | 502 | ```javascript 503 | err = new MultiError([ 504 | new Error('failed to resolve DNS name "abc.example.com"'), 505 | new Error('failed to resolve DNS name "def.example.com"'), 506 | ]); 507 | 508 | console.error(err.message); 509 | ``` 510 | 511 | outputs: 512 | 513 | first of 2 errors: failed to resolve DNS name "abc.example.com" 514 | 515 | See the convenience function `VError.errorFromList`, which is sometimes simpler 516 | to use than this constructor. 517 | 518 | ## Public methods 519 | 520 | 521 | ### `errors()` 522 | 523 | Returns an array of the errors used to construct this MultiError. 524 | 525 | 526 | # Contributing 527 | 528 | See separate [contribution guidelines](CONTRIBUTING.md). 529 | --------------------------------------------------------------------------------