diff --git a/lib/errors/CustomError.js b/lib/errors/CustomError.js new file mode 100644 index 0000000..1868086 --- /dev/null +++ b/lib/errors/CustomError.js @@ -0,0 +1,98 @@ +'use strict' + +var path = require('path') +var PrivateMethodError = require('./PrivateMethodError') + +module.exports = CustomError + +/** + * CustomError + * @class + * @mixin + * @property {String} message The error message + * @property {String} stack The error stack trace + * + * @constructor + * @param {String} [msg] The error message + */ +function CustomError (msg) { + // Error.call(this, msg) + if (msg && typeof msg === 'string') { + this.message = msg + } else if (this.constructor.name !== 'CustomError') { + this.message = global[this.constructor.name].DEFAULT_ERROR_MESSAGE + } else { + this.message = undefined + } + this.createStackTrace() +} + +CustomError.prototype = Object.create(Error.prototype) +CustomError.prototype.constructor = CustomError +CustomError.prototype.createStackTrace = createStackTrace +CustomError.prototype.protect = protect +CustomError.prototype.privatize = privatize + +/** + * Set the the correct stack trace for this error + * @method + * @returns {undefined} + * @throws {PrivateMethodError} + */ +function createStackTrace () { + privatize() + var stack = new Error().stack + var splited = stack.split('\n') + var modifiedStack = splited[0].concat('\n', splited.splice(3).join('\n')) + this.stack = modifiedStack +} + +/** + * Throws a PrivateMethodError if the caller is not CustomError + * @function + * @inner + * @returns {undefined} + * @throws {PrivateMethodError} + */ +function privatize () { + var trace = new Error().stack + var here = getFileCall(trace, 1) + var caller = getFileCall(trace, 3) + if (here !== caller) { + throw new PrivateMethodError() + } +} + +/** + * Throws a ProtectedMethodError if the caller is not a child class of CustomError + * @method + * @returns {undefined} + * @throws {ProtectedMethodError} + */ +function protect (sourcePath) { + var trace = new Error().stack + var caller = getFileCall(trace, 3) + if (sourcePath !== caller) { + // throw new ProtectedMethodError() + throw new Error() + } +} + +/** + * Get the file path of the caller at a given stack level + * @function + * @inner + * @param {String} trace A given stack trace + * @param {Number} level A given stack level to get the file path at this level + * @returns {String} The file path for this level + */ +function getFileCall (trace, level) { + var firstLineOfStack = trace.split('\n')[level] + var splitted = firstLineOfStack.split('(') + firstLineOfStack = splitted.splice(1).join('') + splitted = firstLineOfStack.split(')') + firstLineOfStack = splitted.join('') + splitted = firstLineOfStack.split(':') + firstLineOfStack = splitted.slice(0, -2).join('') + return firstLineOfStack +} diff --git a/tests/errors/CustomError.spec.js b/tests/errors/CustomError.spec.js new file mode 100644 index 0000000..4ea1bcd --- /dev/null +++ b/tests/errors/CustomError.spec.js @@ -0,0 +1,112 @@ +'use strict' + +var path = require('path') +var chai = require('chai') +var CustomError = require(path.resolve('./lib/errors/CustomError')) + +var describe = global.describe +var it = global.it +var expect = chai.expect + +describe('CustomError', function () { + it('should be a function', function () { + expect(CustomError).to.be.a('function') + }) + + it("should not have a 'DEFAULT_ERROR_MESSAGE' static constant property (child classes should have one)", function () { + expect(CustomError).to.not.have.a.property('DEFAULT_ERROR_MESSAGE') + }) + + describe('.prototype', function () { + it('should be a function', function () { + expect(CustomError.prototype).to.be.a('OBJECT') + }) + + it('should be an instance of Error', function () { + expect(CustomError.prototype).to.be.an.instanceof(Error) + }) + + describe('.constructor()', function () { + it('should be an instance of CustomError', function () { + var err = new CustomError() + expect(CustomError.prototype).to.be.an.instanceof(Error) + expect(err).to.be.an.instanceof(CustomError) + }) + + it("should have a 'name' property of type 'String'", function () { + var msg = "'arg' is missing" + var err = new CustomError(msg) + expect(err).to.be.a.property('name') + expect(err.message).to.be.a('String') + }) + + it("should have a 'message' property of type 'String'", function () { + var msg = "'arg' is missing" + var err = new CustomError(msg) + expect(err).to.be.a.property('message') + expect(err.message).to.be.a('String') + expect(err.message).to.equal(msg) + }) + + it("should have a 'stack' property of type 'String'", function () { + var msg = "'arg' is missing" + var err = new CustomError(msg) + expect(err).to.be.a.property('stack') + expect(err.stack).to.be.a('String') + }) + + it("should have a 'createStackTrace' property of type 'function'", function () { + var err = new CustomError('yo') + expect(err).to.have.a.property('createStackTrace') + expect(err.createStackTrace).to.be.a('function') + }) + + it("should have a 'privatize' property of type 'function'", function () { + var err = new CustomError() + expect(err).to.be.a.property('privatize') + expect(err.privatize).to.be.a('function') + }) + + it("should have a 'protect' property of type 'function'", function () { + var err = new CustomError() + expect(err).to.be.a.property('protect') + expect(err.protect).to.be.a('function') + }) + + it('should not fail if an bad type is given instead of a string as first argument', function () { + var err + var func = function () { err = new CustomError(func) } + var obj = function () { err = new CustomError({ foo: 'bar' }) } + var arr = function () { err = new CustomError([ 'foo', 'baz' ]) } + + expect(func).to.not.throw('TypeError') + expect(obj).to.not.throw('TypeError') + expect(arr).to.not.throw('TypeError') + }) + + it('should fails if we call the private createStackTrace method', function () { + var func = function () { + var err = new CustomError(func) + var t = err.createStackTrace() + console.log(t) + } + expect(func).to.throw(Error) + }) + + it('should fails if a child class call the protected createStackTrace method', function () { + var func = function () { + var err = new CustomError(func) + var t = err.createStackTrace() + console.log(t) + } + expect(func).to.throw(Error) + }) + + it('should not have a default error message if no argument is given (child class should have)', function () { + var err = new CustomError() + expect(err.message).to.not.be.a('String') + expect(err.message).to.be.undefined + }) + }) + }) +})