alykoshin/mini-rest-errors

View on GitHub
lib/constructError.js

Summary

Maintainability
B
5 hrs
Test Coverage
var util = require( 'util' );
var _ = require('lodash');
//var verror = require( 'verror' );


function propDefault(target, source, prop) {
    if (typeof source[prop] !== 'function') {
        target[ prop ] = (target[prop] && typeof target[prop] !== 'function') ? target[ prop ] : source[ prop ];
    }
}

function fnResDefault(target, source, prop) {
    if (typeof source[prop] === 'function') {
        target[ prop ] = (target[prop] && typeof target[prop] !== 'function') ? target[ prop ] : source[ prop ].call(target);
        //this[ prop ] = this[ prop ] || defaults[ prop ].call(this);
    }
}

function objDefault(fn, target /*, source,...*/) {
    var prop;
    for (var len=arguments.length,i=2; i<len; ++i) {
        var source = arguments[i];
        for (prop in source) {
            if (source.hasOwnProperty(prop)) {
                fn(target, source, prop);
            }
        }
    }
}

// Idea based on https://github.com/restify/errors/blob/master/lib/index.js
function constructError(name, ParentErrorClass, defaults, preprocessFn, origContext) {
    //assert.string(name, 'name');
    //assert.optionalObject(defaults, 'defaults');

    ParentErrorClass = ParentErrorClass || Error; //verror.WError; //Error;

    // code property doesn't ends with 'Error'; remove it.
    var defaultCode = name.replace(new RegExp('[Ee]rror$'), '');
    var DEFAULT_MESSAGE = 'Unknown Error';

    var baseProps = {
        name: name,
        code: (defaults && defaults.code) || defaultCode,
    };
    var d = _.assign({}, baseProps, defaults);

    // dynamically create a constructor.
    // must be anonymous fn.
    /**
     *
     * @param arguments
     * @constructor
   */
    var ErrorConstructor = function( /* arguments */ ) {
        var callee = arguments.callee; // arguments.callee not accessible in strict mode
        var caller = ErrorConstructor.caller;

        var args, options, context;
        args = Array.prototype.slice.call(arguments);
        //console.log('ErrorConstructor: arguments:', arguments);
        //console.log('ErrorConstructor: args:', args);
        if (typeof preprocessFn === 'function') {
            //args = preprocessFn.apply(this, args);
            var tmp = preprocessFn.apply(this, args);
            options = tmp.options;
            args = tmp.args;
            context = tmp.context;
        } else {
            options = typeof args[ 0 ] === 'string' ? { message: args[ 0 ]} : args[0];
        }
        //var options          = typeof args[ 0 ] === 'string' ? { message: args[ 0 ]} : args[0];
        //var constructContext = args[ 1 ];

        //console.log('options:', options, '; typeof constructContext:', typeof constructContext);

        // call super
        if (ParentErrorClass === Error) {
            Error.call(this, options.message || DEFAULT_MESSAGE);

            //console.log(
            //    '1.options.message:', options.message,
            //    'this.message:', this.message,
            //    ', this.message():', ( (typeof this.message === 'function') ? ( this.message() ) : 'not-a-function')
            //);

        } else if ((typeof verror !== 'undefined') && (ParentErrorClass === verror.WError)) {
            // So far no specific WError handling implemented
            ParentErrorClass.apply(this, options.message || DEFAULT_MESSAGE);

        } else {
            // Assuming arguments for the class under construction are same as for ParentErrorClass
            ParentErrorClass.apply(this, args);
        }

        // Creates the this.stack getter
        //console.log('captureStackTrace: context:', context);
        //console.log('captureStackTrace: origContext:', origContext);
        //console.log('captureStackTrace: caller:', caller);
        //console.log('captureStackTrace: callee:', callee);
        //console.log('captureStackTrace: this.constructor:', this.constructor);
        Error.captureStackTrace(this, context || origContext || caller || this.constructor);

        // Some properties (i.e. message) is already assigned by super - need to save them
        //console.log('propDefault:', propDefault);
        //console.log('this:', this);
        //console.log('defaults:', defaults);
        //console.log('options:', options);
        //objDefault(propDefault, this, defaults, options);
        // When static properties are assigned, assign functions
        //objDefault(fnResDefault, this, defaults, options);

        //console.log('3.options.message:', options.message, 'this.message:', this.message, ', this.message():', typeof this.message === 'function' && this.message());

        _.assign(this, options);
        _.defaults(this, defaults);

    //this.s = this.stack;

    };
    util.inherits(ErrorConstructor, ParentErrorClass);

    // copy over all options to prototype
    //_.assign(ErrorConstructor.prototype, d);
    //ErrorConstructor.prototype.message = d.message;
    ErrorConstructor.prototype.name = d.name;

  ErrorConstructor.prototype.toString = function() {
    var res = util.inspect(this);
    //return JSON.stringify(this);
    if (process.env.NODE_ENV === 'development') {
      res += '\nStack output follows:\n' + this.stack;
    }
    return res;
  };

  // assign display name
    ErrorConstructor.displayName = name;

    // store constructor on main exports
    return ErrorConstructor;
}


module.exports = constructError;