pax_global_header00006660000000000000000000000064124352602010014505gustar00rootroot0000000000000052 comment=a0b8f3beeb97ee041750f617fca12e7a0fc6bcfb errs-0.3.2/000077500000000000000000000000001243526020100124625ustar00rootroot00000000000000errs-0.3.2/.gitignore000066400000000000000000000000441243526020100144500ustar00rootroot00000000000000node_modules .DS_Store npm-debug.logerrs-0.3.2/.travis.yml000066400000000000000000000002051243526020100145700ustar00rootroot00000000000000language: node_js node_js: - 0.8 - 0.10 notifications: email: - travis@nodejitsu.com irc: "irc.freenode.org#nodejitsu" errs-0.3.2/LICENSE000066400000000000000000000021041243526020100134640ustar00rootroot00000000000000Copyright (c) 2012 Charlie Robbins, Nuno Job, and the Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. errs-0.3.2/README.md000066400000000000000000000162571243526020100137540ustar00rootroot00000000000000# errs [![Build Status](https://secure.travis-ci.org/flatiron/errs.png)](http://travis-ci.org/flatiron/errs) Simple error creation and passing utilities focused on: * [Creating Errors](#creating-errors) * [Reusing Error Types](#reusing-types) * [Merging with Existing Errors](#merging-errors) * [Optional Callback Invocation](#optional-invocation) * [Piping Error Events](#piping-errors) ## Creating Errors You should know by now that [a String is not an Error][0]. Unfortunately the `Error` constructor in Javascript isn't all that convenient either. How often do you find yourself in this situation? ``` js var err = new Error('This is an error. There are many like it.'); err.someProperty = 'more syntax'; err.someOtherProperty = 'it wont stop.'; err.notEven = 'for the mayor'; throw err; ``` Rest your fingers, `errs` is here to help. The following is equivalent to the above: ``` js var errs = require('errs'); throw errs.create({ message: 'This is an error. There are many like it.', someProperty: 'more syntax', someOtherProperty: 'it wont stop.', notEven: 'for the mayor' }); ``` ## Reusing Custom Error Types `errs` also exposes an [inversion of control][1] interface for easily reusing custom error types across your application. Custom Error Types registered with `errs` will transparently invoke `Error` constructor and `Error.captureStackTrace` to attach transparent stack traces: ``` js /* * file-a.js: Create and register your error type. * */ var util = require('util'), errs = require('errs'); function MyError() { this.message = 'This is my error; I made it myself. It has a transparent stack trace.'; } // // Alternatively `MyError.prototype.__proto__ = Error;` // util.inherits(MyError, Error); // // Register the error type // errs.register('myerror', MyError); /* * file-b.js: Use your error type. * */ var errs = require('errs'); console.log( errs.create('myerror') .stack .split('\n') ); ``` The output from the two files above is shown below. Notice how it contains no references to `errs.js`: ``` [ 'MyError: This is my error; I made it myself. It has a transparent stack trace.', ' at Object. (/file-b.js:19:8)', ' at Module._compile (module.js:441:26)', ' at Object..js (module.js:459:10)', ' at Module.load (module.js:348:31)', ' at Function._load (module.js:308:12)', ' at Array.0 (module.js:479:10)', ' at EventEmitter._tickCallback (node.js:192:40)' ] ``` ## Merging with Existing Errors When working with errors you catch or are returned in a callback you can extend those errors with properties by using the `errs.merge` method. This will also create a human readable error message and stack-trace: ``` js process.on('uncaughtException', function(err) { console.log(errs.merge(err, {namespace: 'uncaughtException'})); }); var file = fs.createReadStream('FileDoesNotExist.here'); ``` ``` js { [Error: Unspecified error] name: 'Error', namespace: 'uncaughtException', errno: 34, code: 'ENOENT', path: 'FileDoesNotExist.here', description: 'ENOENT, no such file or directory \'FileDoesNotExist.here\'', stacktrace: [ 'Error: ENOENT, no such file or directory \'FileDoesNotExist.here\'' ] } ``` ## Optional Callback Invocation Node.js handles asynchronous IO through the elegant `EventEmitter` API. In many scenarios the `callback` may be optional because you are returning an `EventEmitter` for piping or other event multiplexing. This complicates code with a lot of boilerplate: ``` js function importantFeature(callback) { return someAsyncFn(function (err) { if (err) { if (callback) { return callback(err); } throw err; } }); } ``` `errs` it presents a common API for both emitting `error` events and invoking continuations (i.e. callbacks) with errors. If a `callback` is supplied to `errs.handle()` it will be invoked with the error. It no `callback` is provided then an `EventEmitter` is returned which emits an `error` event on the next tick: ``` js function importantFeature(callback) { return someAsyncFn(function (err) { if (err) { return errs.handle(err, callback); } }); } ``` ## Piping Errors Often when working with streams (especially when buffering for whatever reason), you may have already returned an `EventEmitter` or `Stream` instance by the time an error is handled. ``` js function pipeSomething(callback) { // // You have a stream (e.g. http.ResponseStream) and you // have an optional `callback`. // var stream = new require('stream').Stream; // // You need to do something async which may respond with an // error // getAnotherStream(function (err, source) { if (err) { if (callback) callback(err); } stream.emit('error', err); return; } source.pipe(stream); }) return stream; } ``` You may pass either a `function` or `EventEmitter` instance to `errs.handle`. ``` js function pipeSomething(callback) { // // You have a stream (e.g. http.ResponseStream) and you // have an optional `callback`. // var stream = new require('stream').Stream; // // You need to do something async which may respond with an // error // getAnotherStream(function (err, source) { if (err) { // // Invoke the callback if it exists otherwise the stream. // return errs.handle(err, callback || stream); } source.pipe(stream); }) return stream; } ``` If you wish to invoke both a `callback` function and an `error` event simply pass both: ``` js errs.handle(err, callback, stream); ``` ## Methods The `errs` modules exposes some simple utility methods: * `.create(type, opts)`: Creates a new error instance for with the specified `type` and `opts`. If the `type` is not registered then a new `Error` instance will be created. * `.register(type, proto)`: Registers the specified `proto` to `type` for future calls to `errors.create(type, opts)`. * `.unregister(type)`: Unregisters the specified `type` for future calls to `errors.create(type, opts)`. * `.handle(err, callback)`: Attempts to instantiate the given `error`. If the `error` is already a properly formed `error` object (with a `stack` property) it will not be modified. * `.merge(err, type, opts)`: Merges an existing error with a new error instance for with the specified `type` and `opts`. ## Installation ### Installing npm (node package manager) ``` bash $ curl http://npmjs.org/install.sh | sh ``` ### Installing errs ``` bash $ [sudo] npm install errs ``` ## Tests All tests are written with [vows][2] and should be run with [npm][3]: ``` bash $ npm test ``` #### Author: [Charlie Robbins](http://github.com/indexzero) #### Contributors: [Nuno Job](http://github.com/dscape) #### License: MIT [0]: http://www.devthought.com/2011/12/22/a-string-is-not-an-error/ [1]: http://martinfowler.com/articles/injection.html [2]: https://vowsjs.org [3]: https://npmjs.org errs-0.3.2/examples/000077500000000000000000000000001243526020100143005ustar00rootroot00000000000000errs-0.3.2/examples/async-uncaught-exception.js000066400000000000000000000004611243526020100215640ustar00rootroot00000000000000var fs = require('fs'), errs = require('../lib/errs'); process.on('uncaughtException', function(err) { console.log(errs.merge(err, {namespace: 'uncaughtException'})); }); var file = fs.createReadStream(__filename, {encoding: 'utf8'}); file.on('data', function(b) { throw new Error('Oh Noes'); }); errs-0.3.2/examples/custom-error.js000066400000000000000000000006321243526020100173000ustar00rootroot00000000000000var util = require('util'), errs = require('../lib/errs'); function MyError () { this.message = 'This is my error; I made it myself. It has a transparent stack trace.'; } // // Alternatively `MyError.prototype.__proto__ = Error;` // util.inherits(MyError, Error); // // Register the error type // errs.register('myerror', MyError); console.log( errs.create('myerror') .stack .split('\n') );errs-0.3.2/examples/handling-streams.js000066400000000000000000000006471243526020100201050ustar00rootroot00000000000000var fs = require('fs'), errs = require('../lib/errs'); function safeReadStream(no_such_file, callback) { try { return fs.createReadStream(no_such_file, callback); } catch (err) { return errs.handle(err, callback); } } // would throw, now even without a callback it gets picked as a stream var file = fs.createReadStream('FileDoesNotExist.here'); file.on('error', function (err) { console.log(err); });errs-0.3.2/examples/stack.js000066400000000000000000000002541243526020100157440ustar00rootroot00000000000000var errs = require('../lib/errs'); console.log( errs.create('This is an error. There are many like it. It has a transparent stack trace.') .stack .split('\n') );errs-0.3.2/examples/sync-uncaught-exception.js000066400000000000000000000003541243526020100214240ustar00rootroot00000000000000var fs = require('fs'), errs = require('../lib/errs'); process.on('uncaughtException', function(err) { console.log(errs.merge(err, {namespace: 'uncaughtException'})); }); var file = fs.createReadStream('FileDoesNotExist.here'); errs-0.3.2/lib/000077500000000000000000000000001243526020100132305ustar00rootroot00000000000000errs-0.3.2/lib/errs.js000066400000000000000000000160541243526020100145470ustar00rootroot00000000000000/* * errs.js: Simple error creation and passing utilities. * * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. * MIT LICENSE * */ var events = require('events'), util = require('util'); // // Container for registered error types. // exports.registered = {}; // // Add `Error.prototype.toJSON` if it doesn't exist. // if (!Error.prototype.toJSON) { Object.defineProperty(Error.prototype, 'toJSON', { enumerable: false, writable: true, value: function () { return mixin({ message: this.message, stack: this.stack, arguments: this.arguments, type: this.type }, this); } }); } // // ### function create (type, opts) // #### @type {string} **Optional** Registered error type to create // #### @opts {string|object|Array|function} Options for creating the error: // * `string`: Message for the error // * `object`: Properties to include on the error // * `array`: Message for the error (' ' joined). // * `function`: Function to return error options. // // Creates a new error instance for with the specified `type` // and `options`. If the `type` is not registered then a new // `Error` instance will be created. // exports.create = function createErr(type, opts) { if (!arguments[1] && !exports.registered[type]) { opts = type; type = null; } // // If the `opts` has a `stack` property assume // that it is already an error instance. // if (opts && opts.stack) { return opts; } var message, ErrorProto, error; // // Parse arguments liberally for the message // if (typeof opts === 'function') { opts = opts(); } if (Array.isArray(opts)) { message = opts.join(' '); opts = null; } else if (opts) { switch (typeof opts) { case 'string': message = opts || 'Unspecified error'; opts = null; break; case 'object': message = (opts && opts.message) || 'Unspecified error'; break; default: message = 'Unspecified error'; break; } } // // Instantiate a new Error instance or a new // registered error type (if it exists). // ErrorProto = type && exports.registered[type] || Error; error = new (ErrorProto)(message); if (!error.name || error.name === 'Error') { error.name = (opts && opts.name) || ErrorProto.name || 'Error'; } // // Capture a stack trace if it does not already exist and // remote the part of the stack trace referencing `errs.js`. // if (!error.stack) { Error.call(error); Error.captureStackTrace(error, createErr); } else { error.stack = error.stack.split('\n'); error.stack.splice(1, 1); error.stack = error.stack.join('\n'); } // // Copy all options to the new error instance. // if (opts) { Object.keys(opts).forEach(function (key) { error[key] = opts[key]; }); } return error; }; // // ### function merge (err, type, opts) // #### @err {error} **Optional** The error to merge // #### @type {string} **Optional** Registered error type to create // #### @opts {string|object|Array|function} Options for creating the error: // * `string`: Message for the error // * `object`: Properties to include on the error // * `array`: Message for the error (' ' joined). // * `function`: Function to return error options. // // Merges an existing error with a new error instance for with // the specified `type` and `options`. // exports.merge = function (err, type, opts) { var merged = exports.create(type, opts); // // If there is no error just return the merged one // if (err == undefined || err == null) { return merged; } // // optional stuff that might be created by module // if (!Array.isArray(err) && typeof err === 'object') { Object.keys(err).forEach(function (key) { // // in node v0.4 v8 errors where treated differently // we need to make sure we aren't merging these properties // http://code.google.com/p/v8/issues/detail?id=1215 // if (['stack', 'type', 'arguments', 'message'].indexOf(key)===-1) { merged[key] = err[key]; } }); } // merging merged.name = merged.name || err.name; merged.message = merged.message || err.message; // override stack merged.stack = err.stack || merged.stack; // add human-readable errors if (err.message) { merged.description = err.message; } if (err.stack && err.stack.split) { merged.stacktrace = err.stack.split("\n"); } return merged; }; // // ### function handle (error, callback) // #### @error {string|function|Array|object} Error to handle // #### @callback {function|EventEmitter} **Optional** Continuation or stream to pass the error to. // #### @stream {EventEmitter} **Optional** Explicit EventEmitter to use. // // Attempts to instantiate the given `error`. If the `error` is already a properly // formed `error` object (with a `stack` property) it will not be modified. // // * If `callback` is a function, it is invoked with the `error`. // * If `callback` is an `EventEmitter`, it emits the `error` event on // that emitter and returns it. // * If no `callback`, return a new `EventEmitter` which emits `error` // on `process.nextTick()`. // exports.handle = function (error, callback, stream) { error = exports.create(error); if (typeof callback === 'function') { callback(error); } if (typeof callback !== 'function' || stream) { var emitter = stream || callback || new events.EventEmitter(); process.nextTick(function () { emitter.emit('error', error); }); return emitter; } }; // // ### function register (type, proto) // #### @type {string} **Optional** Type of the error to register. // #### @proto {function} Constructor function of the error to register. // // Registers the specified `proto` to `type` for future calls to // `errors.create(type, opts)`. // exports.register = function (type, proto) { if (arguments.length === 1) { proto = type; type = proto.name.toLowerCase(); } exports.registered[type] = proto; }; // // ### function unregister (type) // #### @type {string} Type of the error to unregister. // // Unregisters the specified `type` for future calls to // `errors.create(type, opts)`. // exports.unregister = function (type) { delete exports.registered[type]; }; // // ### function mixin (target [source0, source1, ...]) // Copies enumerable properties from `source0 ... sourceN` // onto `target` and returns the resulting object. // function mixin(target) { // // Quickly and performantly (in V8) `Arrayify` arguments. // var len = arguments.length, args = new Array(len - 1), i = 1; for (; i < len; i++) { args[i - 1] = arguments[i]; } args.forEach(function (o) { Object.keys(o).forEach(function (attr) { var getter = o.__lookupGetter__(attr), setter = o.__lookupSetter__(attr); if (!getter && !setter) { target[attr] = o[attr]; } else { if (setter) { target.__defineSetter__(attr, setter) } if (getter) { target.__defineGetter__(attr, getter) } } }); }); return target; } errs-0.3.2/package.json000066400000000000000000000010551243526020100147510ustar00rootroot00000000000000{ "name": "errs", "description": "Simple error creation and passing utilities", "version": "0.3.2", "author": "Charlie Robbins ", "maintainers": [ "dscape " ], "repository": { "type": "git", "url": "http://github.com/indexzero/errs.git" }, "keywords": [ "errors", "error", "utilities" ], "devDependencies": { "vows": "0.8.x" }, "main": "./lib/errs", "scripts": { "test": "vows test/*-test.js --spec" }, "engines": { "node": ">= 0.4.0" } } errs-0.3.2/test/000077500000000000000000000000001243526020100134415ustar00rootroot00000000000000errs-0.3.2/test/errs-test.js000066400000000000000000000160661243526020100157400ustar00rootroot00000000000000/* * errs-test.js: Tests for the `errs` module. * * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. * MIT LICENSE * */ var assert = require('assert'), events = require('events'), vows = require('vows'), errs = require('../lib/errs'), fixtures = require('./fixtures'), macros = require('./macros'); var opts = [{ foo: 'bar', status: 404, whatever: 'some other property' }, { testing: true, 'some-string': 'is-a-value', message: 'This is an error. There are many like it.' }, { 'a-function': 'that returns an object', should: true, have: 4, properties: 'yes' }]; vows.describe('errs').addBatch({ "Using errs module": { "the register() method": { "should register the prototype": function () { errs.register('named', fixtures.NamedError); assert.equal(errs.registered['named'], fixtures.NamedError); }, "should register an error without providing its name": function () { errs.register(fixtures.AnError); assert.equal(errs.registered['anerror'], fixtures.AnError); } }, "the create() method with": { "a string": macros.create.string('An error as a string'), "no parameters": macros.create.string('An error as a string'), "an object": { "that has no message": macros.create.object(opts[0]), "that has a message": macros.create.object(opts[1]), "that has a name": { topic : errs.create({name: 'OverflowError'}), "should respect that name in the stack trace" : function (err) { assert.match(err.stack, /^OverflowError/); }, } }, "an error": macros.create.err(new Error('An instance of an error')), "a function": macros.create.fn(function () { return opts[2]; }), "a registered type": { "that exists": macros.create.registered('named', fixtures.NamedError, opts[1]), "that doesnt exist": macros.create.registered('bad', null, opts[1]) } }, "the handle() method": { "with a callback": { topic: function () { var err = this.err = errs.create('Some async error'); errs.handle(err, this.callback.bind(this, null)); }, "should invoke the callback with the error": function (_, err) { assert.equal(err, this.err); } }, "with an EventEmitter (i.e. stream)": { topic: function () { var err = this.err = errs.create('Some emitted error'), stream = new events.EventEmitter(); stream.once('error', this.callback.bind(this, null)); errs.handle(err, stream); }, "should emit the `error` event": function (_, err) { assert.equal(err, this.err); } }, "with a callback and an EventEmitter": { topic: function () { var err = this.err = errs.create('Some emitted error'), stream = new events.EventEmitter(), invoked = 0, that = this; function onError(err) { if (++invoked === 2) { that.callback.call(that, null, err); } } stream.once('error', onError); errs.handle(err, onError, stream); }, "should emit the `error` event": function (_, err) { assert.equal(err, this.err); } }, "with no callback": { topic: function () { var err = this.err = errs.create('Some emitted error'), emitter = errs.handle(err); emitter.once('error', this.callback.bind(this, null)); }, "should emit the `error` event": function (_, err) { assert.equal(err, this.err); } } } } }).addBatch({ "Using errs module": { "the unregister() method": { "should unregister the prototype": function () { errs.unregister('named'); assert.isTrue(!errs.registered['named']); } } } }).addBatch({ "Using errs module": { "the merge() method": { "supports": { "an undefined error": function () { var err = errs.merge(undefined, { message: 'oh noes!' }); assert.equal(err.message, 'oh noes!') assert.instanceOf(err, Error); }, "a null error": function () { var err = errs.merge(null, { message: 'oh noes!' }); assert.equal(err.message, 'oh noes!') assert.instanceOf(err, Error); }, "a false error": function () { var err = errs.merge(false, { message: 'oh noes!' }); assert.equal(err.message, 'oh noes!') assert.instanceOf(err, Error); }, "a string error": function () { var err = errs.merge('wat', { message: 'oh noes!' }); assert.equal(err.message, 'oh noes!'); assert.instanceOf(err, Error); }, }, "should preserve custom properties": function () { var err = new Error('Msg!'); err.foo = "bar"; err = errs.merge(err, {message: "Override!", ns: "test"}); assert.equal(err.foo, "bar"); }, "should have a stack trace": function () { var err = new Error('Msg!'); err = errs.merge(err, {}); assert.isTrue(Array.isArray(err.stacktrace)); }, "should preserve message specified in create": function () { var err = new Error('Msg!'); err = errs.merge(err, {message: "Override!"}); assert.equal(err.message, "Override!"); }, "should preserve properties specified": function () { var err = new Error('Msg!'); err = errs.merge(err, {ns: "test"}); assert.equal(err.ns, "test"); }, "with a truthy value": function () { var err = errs.merge(true, { message: 'Override!', ns: 'lolwut' }) assert.equal(err.message, 'Override!'); assert.equal(err.ns, 'lolwut'); }, "with a truthy stack": function () { var err = errs.merge({ stack: true } , { message: 'Override!', ns: 'lolwut' }) assert.equal(err.message, 'Override!'); assert.equal(err.ns, 'lolwut'); }, "with an Array stack": function () { var err = errs.merge({ stack: [] } , { message: 'Override!', ns: 'lolwut' }) assert.equal(err.message, 'Override!'); assert.equal(err.ns, 'lolwut'); } } } }).addBatch({ "Using errs module": { "Error.prototype.toJSON": { "should exist": function () { assert.isFunction(Error.prototype.toJSON); var json = (new Error('Testing 12345')).toJSON(); ['message', 'stack', 'arguments', 'type'].forEach(function (prop) { assert.isObject(Object.getOwnPropertyDescriptor(json, prop)); }) }, "should be writable": function () { var orig = Error.prototype.toJSON; Error.prototype.toJSON = function() { return 'foo'; }; var json = (new Error('Testing 12345')).toJSON(); assert.equal(json, 'foo'); Error.prototype.toJSON = orig; } } } }).export(module); errs-0.3.2/test/fixtures.js000066400000000000000000000006421243526020100156520ustar00rootroot00000000000000/* * fixtures.js: Test fixtures for the `errs` module. * * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. * MIT LICENSE * */ var util = require('util'); var fixtures = exports; fixtures.NamedError = function NamedError() { this.named = true; }; util.inherits(fixtures.NamedError, Error); fixtures.AnError = function AnError() { this.named = true; }; util.inherits(fixtures.AnError, Error); errs-0.3.2/test/macros.js000066400000000000000000000043511243526020100152660ustar00rootroot00000000000000/* * macros.js: Test macros for the `errs` module. * * (C) 2012, Charlie Robbins, Nuno Job, and the Contributors. * MIT LICENSE * */ var assert = require('assert'), errs = require('../lib/errs'); var macros = exports; function assertTransparentStack(err) { assert.isString(err.stack); err.stack.split('\n').forEach(function (line) { assert.isFalse(/\/lib\/errs\.js\:/.test(line)); }); } // // Macros for `errs.create(type, opts)`. // macros.create = {}; macros.create.string = function (msg) { return { topic: errs.create(msg), "should create an error with the correct message": function (err) { assert.instanceOf(err, Error); assert.equal(msg, err.message); assertTransparentStack(err); } }; }; macros.create.object = function (obj) { return { topic: errs.create(obj), "should create an error with the specified properties": function (err) { assert.instanceOf(err, Error); assert.equal(err.message, obj.message || 'Unspecified error'); assertTransparentStack(err); Object.keys(obj).forEach(function (key) { assert.equal(err[key], obj[key]); }); } }; }; macros.create.err = function (inst) { return { topic: errs.create(inst), "should return the error unmodified": function (err) { assert.equal(err, inst); assertTransparentStack(err); } }; }; macros.create.fn = function (fn) { var obj = fn(); return { topic: errs.create(fn), "should create an error with the specified properties": function (err) { assert.instanceOf(err, Error); assert.equal(err.message, obj.message || 'Unspecified error'); assertTransparentStack(err); Object.keys(obj).forEach(function (key) { assert.equal(err[key], obj[key]); }); } }; }; macros.create.registered = function (type, proto, obj) { return { topic: function () { return errs.create(type, obj); }, "should create an error of the correct type": function (err) { assert.instanceOf(err, proto || Error); assert.equal(err.message, obj.message || 'Unspecified error'); assertTransparentStack(err); Object.keys(obj).forEach(function (key) { assert.equal(err[key], obj[key]); }); } }; };