locustjs-exception
Version:
This library provides Exception classes and utilities regarding errors in javascript programs.
540 lines (470 loc) • 12.8 kB
JavaScript
import { isString, isSomeString, isObject, isNumeric, isEmpty, isFunction, isNull, isDate } from 'locustjs-base';
class StackTraceItem {
constructor(line) {
if (line.length) {
let colonIndex1 = line.lastIndexOf(':');
let colonIndex2 = line.lastIndexOf(':', colonIndex1 - 1);
let openParIndex = line.indexOf('(');
if (openParIndex < 0) {
openParIndex = colonIndex2;
}
let closeParIndex = line.lastIndexOf(')');
if (closeParIndex < 0) {
closeParIndex = line.length;
}
let numbers = line.substr(colonIndex2 + 1, closeParIndex - colonIndex2 - 1);
numbers = numbers.split(':');
this.line = numbers.length > 0 ? parseInt(numbers[0]) : 0;
this.col = numbers.length > 1 ? parseInt(numbers[1]) : 0;
this.callSite = line.substr(0, openParIndex).replace('at ', '').trim();
this.source = line.substr(openParIndex + 1, colonIndex2 - openParIndex - 1);
} else {
this.line = 0;
this.col = 0;
this.callSite = '';
this.source = '';
}
this.message = line;
}
}
class StackTrace {
constructor(stack) {
this.items = [];
if (isSomeString(stack)) {
const lines = stack.split('\n');
if (lines.length) {
for (let i = 1; i < lines.length; i++) {
this.items.push(new StackTraceItem(lines[i]));
}
}
}
}
}
/*
examples:
new Exception({
message: '',
code: 23,
host: '',
data: { name: 'foo' },
stack: '',
inner: new Exception()
})
new Exception(new TypeError('this is an error'))
new Exception(new AnotherException())
*/
function Exception(settings) {
let _message = '';
let _code = undefined;
let _status = undefined;
let _host = undefined;
let _data = null;
let _stack = '';
let _stackTrace = null;
let _inner = null;
let _date = new Date();
let _fileName = undefined;
let _lineNumber = undefined;
let _columnNumber = undefined;
let _name = this.constructor.name;
let _baseName = '';
if (settings instanceof Error) {
_message = settings.message;
_fileName = settings.fileName;
_lineNumber = settings.lineNumber;
_columnNumber = settings.columnNumber;
_baseName = settings.name;
_stack = settings.stack;
} else {
const _settings = Object.assign({}, settings);
_message = isString(_settings.message) ? _settings.message : _message;
_code = isNumeric(_settings.code) ? _settings.code : _code;
_status = isString(_settings.status) ? _settings.status : _status;
_host = isString(_settings.host) ? _settings.host : _host;
_data = _settings.data;
_date = isDate(_settings.date) ? _settings.date : _date;
_stack = isString(_settings.stack) ? _settings.stack : _stack;
_fileName = isString(_settings.fileName) ? _settings.fileName : _fileName;
_lineNumber = isNumeric(_settings.lineNumber) ? _settings.lineNumber : _lineNumber;
_columnNumber = isNumeric(_settings.columnNumber) ? _settings.columnNumber : _columnNumber;
_baseName = isString(_settings.baseName) ? _settings.baseName : _baseName;
if (_settings.innerException) {
if (_settings.innerException instanceof Exception) {
_inner = _settings.innerException;
} else if (_settings.innerException instanceof Error) {
_inner = new Exception(_settings.innerException);
} else {
throw `Exception.ctor: innerException must be an instance of Error or Exception`
}
}
}
if (isEmpty(_stack) && isFunction(Error.captureStackTrace)) {
try {
let temp = {};
Error.captureStackTrace(temp, Exception);
_stack = temp.stack;
if (_stack.startsWith('Error\n')) {
_stack = _message + '\n' + _stack.substr(7);
}
} catch { }
}
_stackTrace = isEmpty(_stack) ? null : new StackTrace(_stack);
const propertyIsReadOnly = (propertyName) => (value) => {
throw new PropertyReadOnlyException(propertyName, _host)
}
Object.defineProperties(this, {
name: {
enumerable: true,
get: function () { return _name },
set: propertyIsReadOnly('Exception.name')
},
baseName: {
enumerable: true,
get: function () { return _baseName },
set: propertyIsReadOnly('Exception.baseName')
},
code: {
enumerable: true,
get: function () { return _code },
set: propertyIsReadOnly('Exception.code')
},
status: {
enumerable: true,
get: function () { return _status },
set: propertyIsReadOnly('Exception.status')
},
host: {
enumerable: true,
get: function () { return _host },
set: propertyIsReadOnly('Exception.host')
},
message: {
enumerable: true,
get: function () { return _message },
set: propertyIsReadOnly('Exception.message')
},
stack: {
enumerable: true,
get: function () { return _stack },
set: propertyIsReadOnly('Exception.stack')
},
stackTrace: {
enumerable: true,
get: function () { return _stackTrace },
set: propertyIsReadOnly('Exception.stackTrace')
},
innerException: {
enumerable: true,
get: function () { return _inner },
set: propertyIsReadOnly('Exception.innerException')
},
data: {
enumerable: true,
get: function () { return _data },
set: propertyIsReadOnly('Exception.data')
},
date: {
enumerable: true,
get: function () { return _date },
set: propertyIsReadOnly('Exception.date')
},
fileName: {
enumerable: true,
get: function () { return _fileName },
set: propertyIsReadOnly('Exception.fileName')
},
lineNumber: {
enumerable: true,
get: function () { return _lineNumber },
set: propertyIsReadOnly('Exception.lineNumber')
},
columnNumber: {
enumerable: true,
get: function () { return _columnNumber },
set: propertyIsReadOnly('Exception.columnNumber')
}
});
}
Exception.prototype.toString = function (separator = '\n') {
const arr = [];
let current = this;
while (current) {
arr.push(current.message);
current = current.innerException;
}
return arr.join(separator);
}
Exception.prototype.flatten = function () {
const arr = [];
let current = this;
while (current) {
arr.push(current);
current = current.innerException;
}
return arr;
}
class PropertyReadOnlyException extends Exception {
constructor(propertyName, host) {
super({
message: `property '${propertyName || '?'}' is read-only.`,
status: 'property-readonly',
host
});
}
}
class AbstractInstantiationException extends Exception {
constructor(type, host) {
super({
message: `cannot instantiate from abstract class '${type || '?'}'.`,
status: 'cannot-instantiate-from-abstract',
host
});
}
}
class NotImplementedException extends Exception {
constructor(method, host) {
super({
message: `method '${method || '?'}' is not implemented.`,
status: 'method-not-implemented',
host
});
}
}
class NotSupportedException extends Exception {
constructor(value, host) {
super({
message: `'${value || '?'}' is not supported.`,
status: 'not-supported',
host
});
}
}
class IndexOutOfRangeException extends Exception {
constructor(index, min, max, host) {
super({
message: `index${(isEmpty(index) ? '': ` '${index}'`)} is out of range [${(min || '0')}, ${max}].`,
status: 'index-out-of-range',
host
});
}
}
class ArgumentNullException extends Exception {
constructor(arg, host) {
super({
message: `argument '${arg || '?'}' cannot be null or undefined.`,
status: 'argument-null',
host
});
}
}
class ArgumentEmptyException extends Exception {
constructor(arg, host) {
super({
message: `argument '${arg || '?'}' cannot be null, undefined or empty strings.`,
status: 'argument-empty',
host
});
}
}
class ArgumentTypeIncorrectException extends Exception {
constructor(arg, type, host) {
super({
message: `argument '${arg || '?'}' has an incorrect type. ${type} expected.`,
status: 'argument-type-incorrect',
host
});
}
}
class NotInstanceOfException extends Exception {
constructor(arg, type, host) {
super({
message: `argument '${arg || '?'}' must be an instance of ${type}`,
status: 'argument-not-instance-of',
host
});
}
}
class InvalidHttpMethodException extends Exception {
constructor(method, host) {
super({
message: `invalid http method '${method || '?'}'. expected GET, POST, PUT or DELETE.`,
status: 'invalid-http-method',
host
});
}
}
function throwIfInstantiateAbstract(classType, instance, host) {
if (instance.constructor === classType) {
throw new AbstractInstantiationException(classType.name, host)
}
}
function throwIfNotInstanceOf(argName, classType, instance, ignoreNull = false, host = '') {
if (isNull(instance)) {
if (ignoreNull) {
return;
} else {
throw new ArgumentNullException(argName, host);
}
}
if (!(instance instanceof classType)) {
throw new NotInstanceOfException(argName, classType.name, host);
}
}
function throwIfNull(arg, argName, host) {
if (isNull(arg)) {
throw new ArgumentNullException(argName, host);
}
}
function throwIfEmpty(arg, argName, host) {
if (isEmpty(arg)) {
throw new ArgumentEmptyException(argName, host);
}
}
function throwIfTypeIncorrect(arg, checkType, host) {
const type = checkType();
if (type) {
throw new ArgumentTypeIncorrectException(arg, type, host);
}
}
function throwIfIndexOutOfRange(index, min, max, host) {
throwIfEmpty(index, 'index', host);
if (!isNumeric(min)) {
min = 0;
}
if (!isNumeric(max)) {
max = 0;
}
if (!isNumeric(index)) {
index = -1;
}
if (index < min || index > max) {
throw new IndexOutOfRangeException(index, min, max, host);
}
}
function throwNotImplementedException(method, host) {
throw new NotImplementedException(method, host);
}
function throwNotSupportedException(member, host) {
throw new NotSupportedException(member, host);
}
function throwPropertyReadOnlyException(propName, host) {
throw new PropertyReadOnlyException(propName, host);
}
class TryCatch {
constructor(fn, context) {
if (!isFunction(fn)) {
throw `TryCatch.ctor: function expected`
}
this.Result = null;
this.Context = context === undefined ? this : context;
this._fn = fn;
this._exception = null;
this._caught = false;
this._finalized = false;
}
Run() {
this.Result = this._fn(this.Context);
}
get Exception() {
return this._exception;
}
set Exception(value) {
if (this._exception === null) {
this._exception = value;
}
}
Catch(exceptionType, fn) {
let raise = this.hasError;
if (raise) {
let callback = fn;
if (this._finalized) {
throw `Catch cannot be used after Finally`
}
if (callback !== undefined) {
if (!isFunction(callback)) {
throw `Catch: callback must be a function`
}
raise = this.Exception instanceof exceptionType;
} else {
callback = exceptionType;
if (!isFunction(callback)) {
throw `Catch: expected callback function`
}
}
if (raise) {
callback(this.Exception, this.Context);
this._caught = true;
}
}
return this;
}
get hasError() {
return this.Exception && !this._caught;
}
Finally(fn) {
if (this.hasError) {
throw this.Exception;
}
if (fn !== undefined) {
if (!isFunction(fn)) {
throw `Finally: callback must be a function`
}
if (!this._finalized) {
fn(this.Context);
this._finalized = true;
}
}
return this;
}
}
const Try = (fn, context) => {
const result = new TryCatch(fn, context);
try {
result.Run();
} catch (e) {
if (e instanceof Exception) {
result.Exception = e;
} else {
result.Exception = new Exception(e);
}
}
return result;
}
const Catch = (x, exceptionType, fn) => {
if (x instanceof TryCatch && x.hasError) {
x.Catch(exceptionType, fn);
}
}
const Finally = (x, fn) => {
if (x instanceof TryCatch) {
x.Finally(fn);
}
}
export {
StackTraceItem,
StackTrace,
Exception,
PropertyReadOnlyException,
AbstractInstantiationException,
NotImplementedException,
NotSupportedException,
ArgumentNullException,
ArgumentEmptyException,
ArgumentTypeIncorrectException,
NotInstanceOfException,
InvalidHttpMethodException,
IndexOutOfRangeException,
throwIfInstantiateAbstract,
throwIfNotInstanceOf,
throwIfNull,
throwIfEmpty,
throwIfTypeIncorrect,
throwNotImplementedException,
throwNotSupportedException,
throwIfIndexOutOfRange,
throwPropertyReadOnlyException,
TryCatch,
Try,
Catch,
Finally
}