verror.js (11865B)
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 };