2.3.0 • Published 10 months ago

@locustjs/exception v2.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
10 months ago

@locustjs/exception

This library provides a base Exception class and a TryCatch utlity class with three Try(), Catch() and Finally() functions.

Current Version

2.2.0

Installation

NPM

npm i @locustjs/exception

Yarn

yarn add @locustjs/exception

Exception class

Exception class provides a better and more meaningful error compared to javascript errors. It is a base class from which all business exceptions could be derived.

constructor

Exception(settings)

parameters:

  • settings: an Exception instance, javascript Error object or a custom object in the shape of such errors.

Properties

PropertyTypeDescriptionexampledefault
namestringexception nameArgumentNullExceptionexception class name
baseNamestringError object from which the exception is createdundefined
messagestringexception or error messageargument 'a' is null or undefinedempty string
codenumbererror code200500undefined
statusstringstatus codeargument-is-nullempty string
hoststringHosting application from which the exception is raisedChrome58.0undefined
dateDatedate/time the exception is raised2020-09-15T13:24:42new Date()
dataobjectcustom data included with the exception{ fn: 'John', ln: 'Doe'}null
stackstringfunction call stack at the time the exception was raisedError.captureStackTrace()
stackTraceStackTracea helper StackTrace object that simplifies iterating in the stack infonull
innerExceptionExceptionan exception by which current exception is raisednull
fileNamestringname of the javascript source file in which the exception was raisedmyapp.jsundefined
lineNumbernumberline number in the javascript source file in which the exception was raised12undefined
columnNumbernumbercolumn number in the javascript source file in which the exception was raised5undefined

Methods

  • toString(separator): converts the exception including its inner exception(s) hierarchy to a string separated by given separator (default = \n).
  • flatten(): converts the hierarchy of inner exceptions to a flattened array.

example 1:

	const ex = new Exception({
		message: 'calling web api failed',
		code: 100324,
		status: 'call-api-failed',
		data: { url: '/api/v1/product', method: 'POST' data: { id: 1, title: 'Product II' } }
	});

example 2:

	try {
		var f;

		console.log(f.test())
	} catch (e) {
		const ex = new Exception(e);

		console.log(JSON.stringify(e));
	}

/*
{
 "name": "Exception",
 "baseName": "TypeError",
 "message": "Cannot read property 'test' of undefined",
 "stack": "TypeError: Cannot read properties of undefined (reading 'test')
    at foo (file:///C:/path/to/my/app/test.html:46:7)
    at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)
    at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)
    at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)",
 "stackTrace": {
 "items": [
	  {
	   "line": 46,
	   "col": 7,
	   "callSite": "foo",
	   "source": "file:///C:/path/to/my/app/test.html",
	   "message": "    at foo (file:///C:/path/to/my/app/test.html:46:7)"
	  },
	  {
	   "line": 51,
	   "col": 7,
	   "callSite": "HTMLButtonElement.<anonymous>",
	   "source": "file:///C:/path/to/my/app/test.html",
	   "message": "    at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)"
	  },
	  {
	   "line": 2,
	   "col": 38053,
	   "callSite": "HTMLButtonElement.dispatch",
	   "source": "https://code.jquery.com/jquery-1.8.3.min.js",
	   "message": "    at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)"
	  },
	  {
	   "line": 2,
	   "col": 33916,
	   "callSite": "HTMLButtonElement.u",
	   "source": "https://code.jquery.com/jquery-1.8.3.min.js",
	   "message": "    at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)\""
	  }
	 ]
	},
 "innerException": null,
 "data": null,
 "date": "2022-10-16T11:42:10.223Z"
}
*/

Derived classes

  • PropertyReadOnlyException
  • AbstractInstantiationException
  • NotImplementedException
  • NotSupportedException
  • IndexOutOfRangeException
  • ValueNotBetweenException
  • ValueIsBetweenException
  • ArgumentNullException
  • ArgumentUndefinedException
  • ArgumentNullOrUndefinedException
  • ArgumentNullOrEmptyException
  • ArgumentEmptyException
  • ArgumentNothingException
  • ArgumentTypeIncorrectException
  • PropertyMissingException
  • ComparisonFailedException
  • NotInstanceOfException
  • InvalidValueException
  • InvalidHttpMethodException

StackTrace

Processes a stack-trace value and populates a list of StackTraceItem objects to facilitate working with a stack-trace information.

By default, Exception class processes stackTrace of a javascript Error object into a StackTrace instance.

constructor

	StackTrace(stackTrace: string)

parameters:

  • stackTrace: strack trace of a javascript error object

Properties

PropertyTypeDescription
itemsStackTraceItemList of stack-trace items

example:

	const st = new StackTrace(`"TypeError: Cannot read properties of undefined (reading 'test')
    at foo (file:///C:/path/to/my/app/test.html:46:7)
    at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)
    at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)
    at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)"`);

	console.log(JSON.stringify(st, null, ' '));

/*
{
 "items": [
  {
   "line": 46,
   "col": 7,
   "callSite": "foo",
   "source": "file:///C:/path/to/my/app/test.html",
   "message": "    at foo (file:///C:/path/to/my/app/test.html:46:7)"
  },
  {
   "line": 51,
   "col": 7,
   "callSite": "HTMLButtonElement.<anonymous>",
   "source": "file:///C:/path/to/my/app/test.html",
   "message": "    at HTMLButtonElement.<anonymous> (file:///C:/path/to/my/app/test.html:51:7)"
  },
  {
   "line": 2,
   "col": 38053,
   "callSite": "HTMLButtonElement.dispatch",
   "source": "https://code.jquery.com/jquery-1.8.3.min.js",
   "message": "    at HTMLButtonElement.dispatch (https://code.jquery.com/jquery-1.8.3.min.js:2:38053)"
  },
  {
   "line": 2,
   "col": 33916,
   "callSite": "HTMLButtonElement.u",
   "source": "https://code.jquery.com/jquery-1.8.3.min.js",
   "message": "    at HTMLButtonElement.u (https://code.jquery.com/jquery-1.8.3.min.js:2:33916)\""
  }
 ]
}
*/

StackTraceItem

Processes one line of a stack-trace information and presents it as an object by extracting its details.

constructor

	StackTraceItem(line: string)

parameters:

  • line: one line of a stack-trace information

Properties

PropertyTypeDescription
linenumberline number of error
colnumbercolumn number of error
callSitestringfunction name at which the error was raised
sourcestringfile name the error was raised at
messagestringgiven error info

example:

	let sti;
	
	sti = new StackTraceItem(`"	at foo (file:///C:/path/to/my/app/test.html:46:7)"`);

	console.log(JSON.stringify(sti, null, ' '));
	/*
	{
		line: 46,
		col: 7,
		callSite: "foo",
		source: "file:///C:/path/to/my/app/test.html"
	}
	*/

	sti = new StackTraceItem(`"	    at <anonymous>:1:10"`);

	console.log(JSON.stringify(sti, null, ' '));
	/*
	{
		line: 1,
		col: 10,
		callSite: "<anonymous>",
		source: ""
	}
	*/

Utility functions

throwIfInstantiateAbstract(classType, instance, host)

Checks whether classType is an abstract class and if so, throws a new AbstractInstantiationException.

class MyAbstractClass {
	constructor() {
		throwIfInstantiateAbstract(MyAbstractClass, this)
	}
	...
}
class MyConcreteClass extends MyAbstractClass {
	...
}

let obj = new MyAbstractClass();   // throws AbstractInstantiationException

throwIfNotInstanceOf(argName, classType, instance, ignoreNull = false, host = '')

Checks whether given object is an instance of the specified classType. If not, it throws a new NotInstanceOfException.

class FooBase {
	get Code() {
		return this._code;
	}
	set Code(value) {
		this._code = value;
	}
	...
}
class Foo extends FooBase {
	...
}

function validate(f) {
	throwIfNotInstanceOf(f, FooBase);   // make sure f is an instance of FooBase

	// f is a FooBase object. so, it definitely has a 'Code' prop.
	
	if (f.Code.length < 3) {
		...
	}
}
  • throwNotImplementedException(method, host) This method is mainly used in abstract classes and throws a new NotImplementedException exception to show that child classes have to implement the method.
class FooBase {
	doSomething() {
		throwNotImplementedException(`${this.constructor.name}.doSomething`)
	}
	...
}
  • throwIfNull(arg, argName, host) Checks arg and throws a ArgumentNullException exception if arg is null.
class FooBase {
	doSomething(foo) {
		throwIfNull(foo, 'foo')

		...
	}
	...
}
  • throwIfUndefined(arg, argName, host) Checks arg and throws a ArgumentUndefinedException exception if arg is undefined.
class FooBase {
	doSomething(foo) {
		throwIfUndefined(foo, 'foo')

		...
	}
	...
}
  • throwIfNullOrUndefined(arg, argName, host) Checks arg and throws a ArgumentNullOrUndefinedException exception if arg is null or undefined.
class FooBase {
	doSomething(foo) {
		throwIfNullOrUndefined(foo, 'foo')

		...
	}
	...
}
  • throwIfNullOrEmpty(arg, argName, host) Checks arg and throws a ArgumentNullOrEmptyException exception if arg is null, undefined or zero-length string.
class FooBase {
	doSomething(foo) {
		throwIfNullOrEmpty(foo, 'foo')

		...
	}
	...
}
  • throwIfEmpty Checks arg and throws a ArgumentEmptyException exception if arg is empty (null, undefined or empty strings).
class FooBase {
	doSomething(foo) {
		throwIfEmpty(foo, 'foo')

		...
	}
	...
}
  • throwIfNothing Checks arg and throws a ArgumentNothingException exception if arg is empty (null, undefined, empty string or emty object).
class FooBase {
	doSomething(foo) {
		throwIfNothing(foo, 'foo')

		...
	}
	...
}
  • throwIfTypeIncorrect(arg, argName, typeOrCheckType, host) Checks arg and throws a ArgumentTypeIncorrectException exception if arg's type does not match typeOrCheckType'.

typeOrCheckType could be a string specifying the type to be checked or a custom function that checks type matching.

Intrinsic supported types:

  • number: number
  • number+: non-zero number
  • numeric: number or a string containing a number
  • int or integer : integer
  • int+ or integer+: a non-zero integer
  • float: float
  • float+: non-zero float
  • string: string
  • string+: non-zero length string
  • bool: bool
  • bool*: bool or a non-trimmed string containing true, false, True, False, TRUE, FALSE
  • bool#: bool or a string containing true, false, True, False, TRUE, FALSE
  • bool^: bool or a string containing true, false, True, False
  • bool!: bool or a string containing true, false
  • array: array
  • array+: non-zero length array
  • object: object
  • object+: non-empty object
  • date: date
  • function: function
  • basic: number, string, bool, date
  • primitive: basic data type or instances of Number, String, Boolean, Date
  • other: arg should be an instance of
class Foo { }
class Bar { }
...
function doSomething(a, b, c, d, e) {
	throwIfTypeIncorrect(a, 'a', 'Foo')
	throwIfTypeIncorrect(b, 'b', 'bool')
	throwIfTypeIncorrect(c, 'c', 'number')
	throwIfTypeIncorrect(d, 'd', 'string')
	throwIfTypeIncorrect(e, 'e', x => x instanceof Bar)
	...
}

By default, throwIfTypeIncorrect requires the argument not to be null or undefined. Using the ? suffix, we can make the argument optional.

function doSomething(a, b) {
	throwIfTypeIncorrect(a, 'a', 'number?')
	throwIfTypeIncorrect(b, 'b', 'string+?')
	...
}

List of helpers created over throwIfTypeIncorrect:

  • throwIfNotNumber
  • throwIfNotSomeNumber
  • throwIfNotNumeric
  • throwIfNotBool
  • throwIfNotHasBool
  • throwIfNotString
  • throwIfNotSomeString
  • throwIfNotDate
  • throwIfNotObject
  • throwIfNotSomeObject
  • throwIfNotFunction
  • throwIfNotInt
  • throwIfNotSomeInt
  • throwIfNotFloat
  • throwIfNotSomeFloat
  • throwIfNotArray
  • throwIfNotSomeArray
  • throwIfNotBasic
  • throwIfNotPrimitive
  • throwIfNotInShape

All of the helpers, except throwIfNotInShape have the following signature:

fn(arg, argName, host)

example:

function sum(arr, fromIndex, toIndex) {
	throwIfNotArray(arr, 'arr')
	throwIfNotNumber(fromIndex, 'fromIndex')
	throwIfNotNumber(toIndex, 'toIndex')

	// we are safe here
	...
}

throwIfNotInShape signature is as follows:

throwIfNotInShape(arg, argName, shape, host) example:

function validate(person) {
	throwIfNotInShape(person, 'person', {
		firstName: 'string+?',	// not required
		lastName: 'string+',	// required
		age: 'int?',	// not required
		phone: {
			required: true,
			validate: x => /^\d+$/.test(x)
		},
		location: {
			shape: {
				country: 'string',
				city: 'string'
			}
		},
		scores: {
			array: true,
			type: 'int',
			validate: x => x >= 0 && x <= 100
		}
	})

	// we are safe here
	...
}
  • throwIfIndexOutOfRange(index, min, max, host) Checks index and throws a IndexOutOfRangeException exception if index is not between min (inclusive) and max (exclusive).
class Foo { }
...
function concat(arr, from, to) {
	throwIfIndexOutOfRange(from, `from`, 0, arr.length);
	throwIfIndexOutOfRange(to, `to`, 0, arr.length);
	...
}
  • throwIfMissingProperty(obj, objName, prop, host) Checks whether obj contains a property named prop and if not throws a PropertyMissingException exception.
...
function login(credentials) {
	throwIfMissingProperty(credentials, `credentials`, 'username');
	throwIfMissingProperty(credentials, `credentials`, 'password');
	...
}
  • throwIfComparisonFailed(arg) Checks arg.a and arg.b and throws a ComparisonFailedException exception if comparing arg.a and arg.b based on arg.operator does not succeed.

Structure of arg:

{
	a: 10,
	b: 20,
	operator: '<'
}

Example:

...
function concat(arr, from, to) {
	throwIfComparisonFailed({ a: from, b: to, operator: '>' });
	...
}

List of helpers created over throwIfComparisonFailed:

  • throwIfLessThan(a, b)
  • throwIfLessThanOrEqualTo(a, b)
  • throwIfGreaterThan(a, b)
  • throwIfGreaterThanOrEqualTo(a, b)
  • throwIfEqualTo(a, b)
  • throwIfNotEqualTo(a, b)
  • throwIfTypeEqualTo(a, b)
  • throwIfNotTypeEqualTo(a, b)
  • throwIfNotBetween(x, from , to)
  • throwIfBetween(x, from , to)

Example:

...
function concat(arr, from, to) {
	throwIfGreaterThan(from, to);
	...
}

Try/Catch/Finally

examples

Assume we have the following functions:

function f1(a) { return a.length }
function f2(a) { console.log(f1(a)) }

The ordinary try/catch block is this way:

	try {
		f2();
	} catch (e) {
		console.log(e);
	} finally {
		console.log('Finished.')
	}

Here is various examples of using Try/Catch:

Example 1 (basic):

 Try(_ => f2())
.Catch(e => console.log(e))
.Finally(_ => console.log('Finished.'))

Try/Catch provides multiple catch ability, a feature javascript try/catch does not support.

Example 2:

function f1(a) {
	if (a === null) {
		throw new ArgumentNullException('a');
	} else if (a === undefined) {
		throw new ArgumentUndefinedException('a');
	} else if (!isSomeString(a)) {
		throw new ArgumentTypeIncorrectException('a', 'string');
	}
	
	return a.length
}

function f2(a) {
	console.log(f1(a))
}

 Try(f1)
.Catch(ArgumentNullException, console.log)
.Catch(ArgumentUndefinedException, console.log)
.Catch(ArgumentTypeIncorrectException, console.log)
.Catch(e => console.log(`Other exception raised: ${e}`))	// this Catch() is not triggered
.Finally(_ => console.log('Finished.'))

Example 3: verbose

const x = Try(f2);

console.log('do something outside of previous Try');

x.Catch(e => console.log(e));
x.Finally(_ => console.log('Finished.'));

/* output:
TypeError: Cannot read property 'length' of undefined
    at f2 (<anonymous>:1:69)
    at f1 (<anonymous>:1:30)
    at TryCatch._fn (<anonymous>:1:19)
    at TryCatch.Run (<anonymous>:93:22)
    at Try (<anonymous>:161:16)
    at <anonymous>:1:9
    at <anonymous>:3:42
*/

Example 4: external catch (not possible using traditional try/catch)

function doSomething(a) {
	return Try(_ => {
		console.log(`name: ${a.name}`);
	});
}

doSomething()
	.Catch(e => console.log(e))
	.Finally('Finished');