/* * verror.js: richer JavaScript errors */ var mod_assertplus = require('assert-plus'); var mod_util = require('util'); var mod_extsprintf = require('extsprintf'); var mod_isError = require('core-util-is').isError; var sprintf = mod_extsprintf.sprintf; /* * Public interface */ /* So you can 'var VError = require('verror')' */ module.exports = VError; /* For compatibility */ VError.VError = VError; /* Other exported classes */ VError.SError = SError; VError.WError = WError; VError.MultiError = MultiError; /* * Common function used to parse constructor arguments for VError, WError, and * SError. Named arguments to this function: * * strict force strict interpretation of sprintf arguments, even * if the options in "argv" don't say so * * argv error's constructor arguments, which are to be * interpreted as described in README.md. For quick * reference, "argv" has one of the following forms: * * [ sprintf_args... ] (argv[0] is a string) * [ cause, sprintf_args... ] (argv[0] is an Error) * [ options, sprintf_args... ] (argv[0] is an object) * * This function normalizes these forms, producing an object with the following * properties: * * options equivalent to "options" in third form. This will never * be a direct reference to what the caller passed in * (i.e., it may be a shallow copy), so it can be freely * modified. * * shortmessage result of sprintf(sprintf_args), taking options.strict * into account as described in README.md. */ function parseConstructorArguments(args) { var argv, options, sprintf_args, shortmessage, k; mod_assertplus.object(args, 'args'); mod_assertplus.bool(args.strict, 'args.strict'); mod_assertplus.array(args.argv, 'args.argv'); argv = args.argv; /* * First, figure out which form of invocation we've been given. */ if (argv.length === 0) { options = {}; sprintf_args = []; } else if (mod_isError(argv[0])) { options = { 'cause': argv[0] }; sprintf_args = argv.slice(1); } else if (typeof (argv[0]) === 'object') { options = {}; for (k in argv[0]) { options[k] = argv[0][k]; } sprintf_args = argv.slice(1); } else { mod_assertplus.string(argv[0], 'first argument to VError, SError, or WError ' + 'constructor must be a string, object, or Error'); options = {}; sprintf_args = argv; } /* * Now construct the error's message. * * extsprintf (which we invoke here with our caller's arguments in order * to construct this Error's message) is strict in its interpretation of * values to be processed by the "%s" specifier. The value passed to * extsprintf must actually be a string or something convertible to a * String using .toString(). Passing other values (notably "null" and * "undefined") is considered a programmer error. The assumption is * that if you actually want to print the string "null" or "undefined", * then that's easy to do that when you're calling extsprintf; on the * other hand, if you did NOT want that (i.e., there's actually a bug * where the program assumes some variable is non-null and tries to * print it, which might happen when constructing a packet or file in * some specific format), then it's better to stop immediately than * produce bogus output. * * However, sometimes the bug is only in the code calling VError, and a * programmer might prefer to have the error message contain "null" or * "undefined" rather than have the bug in the error path crash the * program (making the first bug harder to identify). For that reason, * by default VError converts "null" or "undefined" arguments to their * string representations and passes those to extsprintf. Programmers * desiring the strict behavior can use the SError class or pass the * "strict" option to the VError constructor. */ mod_assertplus.object(options); if (!options.strict && !args.strict) { sprintf_args = sprintf_args.map(function (a) { return (a === null ? 'null' : a === undefined ? 'undefined' : a); }); } if (sprintf_args.length === 0) { shortmessage = ''; } else { shortmessage = sprintf.apply(null, sprintf_args); } return ({ 'options': options, 'shortmessage': shortmessage }); } /* * See README.md for reference documentation. */ function VError() { var args, obj, parsed, cause, ctor, message, k; args = Array.prototype.slice.call(arguments, 0); /* * This is a regrettable pattern, but JavaScript's built-in Error class * is defined to work this way, so we allow the constructor to be called * without "new". */ if (!(this instanceof VError)) { obj = Object.create(VError.prototype); VError.apply(obj, arguments); return (obj); } /* * For convenience and backwards compatibility, we support several * different calling forms. Normalize them here. */ parsed = parseConstructorArguments({ 'argv': args, 'strict': false }); /* * If we've been given a name, apply it now. */ if (parsed.options.name) { mod_assertplus.string(parsed.options.name, 'error\'s "name" must be a string'); this.name = parsed.options.name; } /* * For debugging, we keep track of the original short message (attached * this Error particularly) separately from the complete message (which * includes the messages of our cause chain). */ this.jse_shortmsg = parsed.shortmessage; message = parsed.shortmessage; /* * If we've been given a cause, record a reference to it and update our * message appropriately. */ cause = parsed.options.cause; if (cause) { mod_assertplus.ok(mod_isError(cause), 'cause is not an Error'); this.jse_cause = cause; if (!parsed.options.skipCauseMessage) { message += ': ' + cause.message; } } /* * If we've been given an object with properties, shallow-copy that * here. We don't want to use a deep copy in case there are non-plain * objects here, but we don't want to use the original object in case * the caller modifies it later. */ this.jse_info = {}; if (parsed.options.info) { for (k in parsed.options.info) { this.jse_info[k] = parsed.options.info[k]; } } this.message = message; Error.call(this, message); if (Error.captureStackTrace) { ctor = parsed.options.constructorOpt || this.constructor; Error.captureStackTrace(this, ctor); } return (this); } mod_util.inherits(VError, Error); VError.prototype.name = 'VError'; VError.prototype.toString = function ve_toString() { var str = (this.hasOwnProperty('name') && this.name || this.constructor.name || this.constructor.prototype.name); if (this.message) str += ': ' + this.message; return (str); }; /* * This method is provided for compatibility. New callers should use * VError.cause() instead. That method also uses the saner `null` return value * when there is no cause. */ VError.prototype.cause = function ve_cause() { var cause = VError.cause(this); return (cause === null ? undefined : cause); }; /* * Static methods * * These class-level methods are provided so that callers can use them on * instances of Errors that are not VErrors. New interfaces should be provided * only using static methods to eliminate the class of programming mistake where * people fail to check whether the Error object has the corresponding methods. */ VError.cause = function (err) { mod_assertplus.ok(mod_isError(err), 'err must be an Error'); return (mod_isError(err.jse_cause) ? err.jse_cause : null); }; VError.info = function (err) { var rv, cause, k; mod_assertplus.ok(mod_isError(err), 'err must be an Error'); cause = VError.cause(err); if (cause !== null) { rv = VError.info(cause); } else { rv = {}; } if (typeof (err.jse_info) == 'object' && err.jse_info !== null) { for (k in err.jse_info) { rv[k] = err.jse_info[k]; } } return (rv); }; VError.findCauseByName = function (err, name) { var cause; mod_assertplus.ok(mod_isError(err), 'err must be an Error'); mod_assertplus.string(name, 'name'); mod_assertplus.ok(name.length > 0, 'name cannot be empty'); for (cause = err; cause !== null; cause = VError.cause(cause)) { mod_assertplus.ok(mod_isError(cause)); if (cause.name == name) { return (cause); } } return (null); }; VError.hasCauseWithName = function (err, name) { return (VError.findCauseByName(err, name) !== null); }; VError.fullStack = function (err) { mod_assertplus.ok(mod_isError(err), 'err must be an Error'); var cause = VError.cause(err); if (cause) { return (err.stack + '\ncaused by: ' + VError.fullStack(cause)); } return (err.stack); }; VError.errorFromList = function (errors) { mod_assertplus.arrayOfObject(errors, 'errors'); if (errors.length === 0) { return (null); } errors.forEach(function (e) { mod_assertplus.ok(mod_isError(e)); }); if (errors.length == 1) { return (errors[0]); } return (new MultiError(errors)); }; VError.errorForEach = function (err, func) { mod_assertplus.ok(mod_isError(err), 'err must be an Error'); mod_assertplus.func(func, 'func'); if (err instanceof MultiError) { err.errors().forEach(function iterError(e) { func(e); }); } else { func(err); } }; /* * SError is like VError, but stricter about types. You cannot pass "null" or * "undefined" as string arguments to the formatter. */ function SError() { var args, obj, parsed, options; args = Array.prototype.slice.call(arguments, 0); if (!(this instanceof SError)) { obj = Object.create(SError.prototype); SError.apply(obj, arguments); return (obj); } parsed = parseConstructorArguments({ 'argv': args, 'strict': true }); options = parsed.options; VError.call(this, options, '%s', parsed.shortmessage); return (this); } /* * We don't bother setting SError.prototype.name because once constructed, * SErrors are just like VErrors. */ mod_util.inherits(SError, VError); /* * Represents a collection of errors for the purpose of consumers that generally * only deal with one error. Callers can extract the individual errors * contained in this object, but may also just treat it as a normal single * error, in which case a summary message will be printed. */ function MultiError(errors) { mod_assertplus.array(errors, 'list of errors'); mod_assertplus.ok(errors.length > 0, 'must be at least one error'); this.ase_errors = errors; VError.call(this, { 'cause': errors[0] }, 'first of %d error%s', errors.length, errors.length == 1 ? '' : 's'); } mod_util.inherits(MultiError, VError); MultiError.prototype.name = 'MultiError'; MultiError.prototype.errors = function me_errors() { return (this.ase_errors.slice(0)); }; /* * See README.md for reference details. */ function WError() { var args, obj, parsed, options; args = Array.prototype.slice.call(arguments, 0); if (!(this instanceof WError)) { obj = Object.create(WError.prototype); WError.apply(obj, args); return (obj); } parsed = parseConstructorArguments({ 'argv': args, 'strict': false }); options = parsed.options; options['skipCauseMessage'] = true; VError.call(this, options, '%s', parsed.shortmessage); return (this); } mod_util.inherits(WError, VError); WError.prototype.name = 'WError'; WError.prototype.toString = function we_toString() { var str = (this.hasOwnProperty('name') && this.name || this.constructor.name || this.constructor.prototype.name); if (this.message) str += ': ' + this.message; if (this.jse_cause && this.jse_cause.message) str += '; caused by ' + this.jse_cause.toString(); return (str); }; /* * For purely historical reasons, WError's cause() function allows you to set * the cause. */ WError.prototype.cause = function we_cause(c) { if (mod_isError(c)) this.jse_cause = c; return (this.jse_cause); };