Source: errorme.js

'use strict';
/**
 * @license
 * MIT License
 *
 * Copyright (c) 2016 [Karlen Manaseryan]
 *	
 * 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
 * IABILITY, 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.
 * 
 * */


/**
 * Object which describes an HTTP error information. 
 * @typedef {Object} httpError
 * @property {number} code - HTTP error code
 * @property {number} definedCode - defined error code
 * @property {string} message - Error inormation
 */


const DEFAULT = require('./constants/default.json');
const HTTP_ERROR_CODES = require('./constants/http.json');
const ERROR_LANGUAGES = DEFAULT.ERROR_LANGUAGES;

let _errors = DEFAULT.ERRORS;
let initialized = false;
let showLogs = false;

var v = require('./internal/validate');



var _getErrorByName = (name)=>{
	let error =  _errors[name];
	if(!error) return null;
	error.name = name;
	return error;
}

var _getErrorByCode = (code)=>{
	let error;
	
	for(let key in _errors){
		let err = _errors[key];
		if(err.CODE == code){
			error = err;
			error.name = key;
		}
	}
	if(!error) return null;
	
	return error;
}

var _makeHttpError =  (code, message) => {
	let httpCode = HTTP_ERROR_CODES["ERROR_" + code];
	let error;
	try {
		code = httpCode.CODE;
		error = {
			code: code,
			message: httpCode.MESSAGE
		}
		if(process.env.DEV && JSON.parse(process.env.DEV)){
			error.message = message || httpCode.MESSAGE;
		}
	} catch (error) {
		let err = new RangeError("The provided [code] http code: " + JSON.stringify(code)
			+ " is not valid. Please check the http config file under the /config/http folder.");
		throw err;
	}
	return error;
}

var _parseErrorTo = (error, lang)=>{
	lang = lang.toLowerCase();
	let isValidLang = false;
	for(let key in ERROR_LANGUAGES){
		let LANG = ERROR_LANGUAGES[key];
		if(LANG == lang){
			isValidLang = true;
		}
	}
	if(!isValidLang){
		let e = new ReferenceError("The error object can't be get for the provided error language");
		throw e;
	}
	if(lang == 'http'){
		let httpError = _makeHttpError(error.httpCode, error.message);
		httpError.definedCode = error.code;
		return httpError;
	}
}


/**
 * @class 
 * @author Karlen Manaseryan <kmanaseryan@gmail.com>
 */
class Errorme {
	constructor(app){
		this.app = app;
	}
	
	/**
	* memberof Errorme#
	* @param {string} key - The name (key) of error object or the error code inside [ERRORS] object 
	* @param {string} message - An optional parameter for describing the error
	* @description Returns error object based on the defined errors list
	* @returns {ErrormeError}
	* @author Karlen Manaseryan <kmanaseryan@gmail.com>
	*/
	getError(key, message){
		let error;
		error = _getErrorByCode(key);
		if(!error)
			error = _getErrorByName(key);
		if(!error){
			let e = new ReferenceError("The error object can't be get for the provided error name/code");
			Error.captureStackTrace(e, this.getError);
			throw e;
		}
		error = new ErrormeError(error, this.getError, message);
		if(showLogs)
			console.warn("[errorme] Error code: " + error.code + ", Message: " + (error.message || error.defaultMessage))
		return error;
	}
	/**
	* memberof Errorme#
	* @param {string} key - The name (key) of error object or the error code inside [ERRORS] object 
	* @param {string} message - An optional parameter for describing the error
	* @description Returns http specific error object by the key name or the error code in [ERRORS] varant object
	* @returns {httpError}
	* @author Karlen Manaseryan <kmanaseryan@gmail.com>
	*/
	getHttpError(key, message){
		return this.getError(key, message).parseTo('http');
	}
	
	/**
	* memberof Errorme#
	* @param {function} app - express middleware app
	* @description Assign a function to errormeSend property of response object.
	* The function errormeSend() is the following: errormeSend(error: ErrormeError, data: Object), if error exist then send the appropriate http error, 
	* otherwhise sends success with the provided data  
	* @author Karlen Manaseryan <kmanaseryan@gmail.com>
	*/
	middleware(app){
		if(!(app && typeof app.use == 'function')){
			let e = Error("App argument should be valid express app");
			Error.captureStackTrace(e, this.middleware);
			throw e;
		}
		app.use((req, res)=>{
			res.errormeSend = (err, data)=>{
				if(err && !(err instanceof ErrormeError)){
					let e = Error("error argument is not instance of ErrormeError class");
					Error.captureStackTrace(e, res.errormeSend);
					throw e;
				}
				if(err){
					err = err.parseTo('http');
					let response = {
						error:{
							code: err.definedCode,
							message: err.message
						},
						status: err.code,
						data: null
					}
					res.status(err.code);
					res.send(JSON.stringify(response));	
				}else{
					let response = {
						error: null,
						data: data
					}
					res.status(200);
					res.send(JSON.stringify(response));
				}
			}
		});
	}
}


/**
 * @class
 * @classdesc - Class inherited from Error class. 
 * @author Karlen Manaseryan <kmanaseryan@gmail.com>
 */
class ErrormeError extends Error{
	constructor(error, stack, message){
		super(message || error.DEFAULT_MESSAGE);
		this.name = "Errorme" + error.name;
		this.code = error.CODE;
		this.defaultMessage = error.DEFAULT_MESSAGE;
		this.httpCode = error.HTTP_CODE;
		Error.captureStackTrace(this, stack);
	}
	
	/**
	* memberof ErrormeError#
	* @param {string} lang - The error language in which it is required to parse
	* @description Parse the provided error into provided type (ex. http) specific object
	* @returns {httpError}
	* @author Karlen Manaseryan <kmanaseryan@gmail.com>
	*/
	parseTo(lang){
		return _parseErrorTo(this, lang);
	}
}

/**
 * errorme module.
 * @module errorme
 */

/**
 * Requiring module. Extra errors can be added only once i.e. at the first require(). 
 * Adding after initialization i.e. during next time require() will throw error.
 *
 * @param {Object|array} errorsToBeAdded - Either array or key/value array which will have information
 * of new errors. All errors should have unique name and code. The style of objects should be the same as in /constants/default.json file.
 * If the argument is array then every object of array should have extra "NAME" property which should be unique.
 * @param {Object} option - Optional argument
 * @param {boolean} option.overwrite - Whether to overwrite default defined errors list with the new provided ones or not.
 * @param {boolean} option.showLogs - Whether to show logs or not. Logs show error message and error code once the getError() function being called
 * @return {errorme}
 * @author Karlen Manaseryan <kmanaseryan@gmail.com>
 */
module.exports = (errorsToBeAdded, option)=>{
	if(!initialized){
		let newErrors = _errors;
		option = option || {};
		showLogs = option.showLogs;
		if(option.overwrite){
			newErrors = [];
		}
		for(let key in errorsToBeAdded){
			let error = errorsToBeAdded[key];
			if(isNaN(parseInt(key)))
				error.NAME = key;
			error = v.error(error, newErrors, module.exports);
			let toBeAddedErrorName = error.NAME;
			error = {
				CODE: error.CODE,
				DEFAULT_MESSAGE: error.DEFAULT_MESSAGE,
				HTTP_CODE: error.HTTP_CODE
			}
			newErrors[toBeAddedErrorName] = error;
		}
		_errors = newErrors;
	}else if(errorsToBeAdded){
		throw new Error("errorme is already initialized with errors argument");
	}
	initialized = true;	
	return new Errorme();
};