event_request
Version:
A Backend Server
236 lines (201 loc) • 6.77 kB
JavaScript
/**
* @brief Error handler responsible for handling errors thrown by the EventRequest
*/
class ErrorHandler {
constructor() {
this.namespaces = new Map();
this.defaultNamespace = {
callback : this._defaultCase.bind( this ),
status : 500,
code : ErrorHandler.GENERAL_ERROR_CODE,
emit : true,
message : undefined,
headers : undefined,
formatter : ( { code, message } ) => {
const response = { error: { code } };
if ( message !== null && message !== undefined )
response.error.message = message;
return response;
}
};
}
/**
* @brief Handles an error
*
* @param {EventRequest} event
* @param {*} [errorToHandle=null]
* @param {Number} [errStatusCode=null]
* @param {Boolean} [emitError=null]
*
* @return {Promise}
*/
async handleError( event, errorToHandle = null, errStatusCode = null, emitError = null ) {
let errorNamespace = null;
let code;
let error;
let message;
let status;
let headers;
let emit;
let formatter;
if ( errorToHandle !== null && typeof errorToHandle === 'object' && typeof errorToHandle.code === 'string' ) {
code = errorToHandle.code;
error = errorToHandle.error || null;
message = errorToHandle.message || errorToHandle.error || null;
status = errorToHandle.status || errStatusCode;
headers = errorToHandle.headers || null;
formatter = typeof errorToHandle.formatter === 'function' ? errorToHandle.formatter : null;
emit = typeof errorToHandle.emit === 'boolean' ? errorToHandle.emit : emitError;
}
else {
code = ErrorHandler.GENERAL_ERROR_CODE;
error = errorToHandle;
status = errStatusCode;
headers = null;
emit = emitError;
if ( errorToHandle instanceof Error ) {
errorNamespace = this.getNamespace( errorToHandle.message );
if ( errorNamespace !== null )
code = errorToHandle.message;
else
message = errorToHandle.message;
}
else if ( typeof errorToHandle === 'string' ) {
errorNamespace = this.getNamespace( errorToHandle );
if ( errorNamespace !== null )
code = errorToHandle;
else
message = errorToHandle;
}
else
message = errorToHandle;
}
if ( errorNamespace === null )
errorNamespace = this.getNamespace( code ) || this.defaultNamespace;
const callback = errorNamespace.callback;
message = this._formatError( message || errorNamespace.message );
status = status || errorNamespace.status;
emit = typeof emit === 'boolean' ? emit : errorNamespace.emit;
headers = headers || errorNamespace.headers || {};
formatter = formatter || errorNamespace.formatter;
await callback( { event, code, status, message, error, headers, emit, formatter } );
}
/**
* @brief Adds a new namespace
*
* @param {String} code
* @param {Object} object
* @param {*} object.message
* @param {Function} object.callback
* @param {Number} object.status
* @param {Boolean} object.emit
* @param {Object} object.headers
* @param {Function} object.formatter
*/
addNamespace( code, { message, callback, status, emit, headers, formatter } = {} ) {
if ( ! this.validateNamespaceCode( code ) )
return;
if ( typeof callback !== 'function' )
callback = this.defaultNamespace.callback;
if ( typeof message !== 'string' )
message = this.defaultNamespace.message;
if ( typeof status !== 'number' )
status = this.defaultNamespace.status;
if ( typeof emit !== 'boolean' )
emit = this.defaultNamespace.emit;
if ( typeof headers !== 'object' )
headers = this.defaultNamespace.headers;
if ( typeof formatter !== 'function' )
formatter = this.defaultNamespace.formatter;
this.namespaces.set( code, { message, callback, status, emit, code, headers, formatter } );
}
/**
* @brief Validates that the provided errorCode is a string and does not have any white space characters
*
* @param {String} errorCode
*
* @return {Boolean}
*/
validateNamespaceCode( errorCode ) {
return typeof errorCode === 'string' && errorCode.match( /\s/g ) === null;
}
/**
* @brief Returns the expectation that contains the errorCode and message
*
* @param {String} errorCode
*
* @return {Object|null}
*/
getNamespace( errorCode ) {
if ( ! this.validateNamespaceCode( errorCode ) )
return null;
while ( true ) {
if ( this.namespaces.has( errorCode ) )
return this.namespaces.get( errorCode );
const parts = errorCode.split( '.' );
parts.pop();
if ( parts.length === 0 )
return this.defaultNamespace;
errorCode = parts.join( '.' );
}
}
/**
* @brief Fallback namespace that will be called whenever there is no namespace for the given error code OR the namespace match does not have a callback
*
* @private
* @details This callback will set any headers provided
* This callback will set the status code given
* This callback will format an appropriate message in case of an error
* This callback emit an error IF needed
*
* @param {Object} object
* @param {EventRequest} object.event - the EventRequest object
* @param {String} object.code - error code to send
* @param {Number} object.status - status code to return
* @param {*} object.error - the error message or object
* @param {*} object.message - the error message -> this will take precedence over the error in regards to the message
* @param {Object} object.headers - extra headers to set
* @param {Boolean} object.emit - Flag to emit the error or not
* @param {Function} object.formatter - The error formatter
*
* @return {Promise}
*/
async _defaultCase( { event, code, status, error, message, headers, emit, formatter } ) {
if ( event.isFinished() )
return;
const toEmit = { code, status, headers };
if ( message !== null && message !== undefined )
toEmit.message = message;
if ( error !== null && error !== undefined )
toEmit.error = error;
if ( emit )
event.emit( 'on_error', toEmit );
for ( const key in headers ) {
/* istanbul ignore next */
if ( ! {}.hasOwnProperty.call( headers, key ) )
continue;
event.setResponseHeader( key, headers[key] );
}
const result = formatter( { event, code, status, error, message, headers, emit } );
if ( result instanceof Promise )
await event.send( await result, status );
else
await event.send( result, status );
}
/**
* @brief Formats the error in a presentable format
*
* @private
* @param {*} error - the error to format ( will extract the message if Error object, otherwise return )
*
* @return {*}
*/
_formatError( error ) {
if ( error instanceof Error )
return error.message;
return error;
}
}
ErrorHandler.GENERAL_ERROR_CODE = 'app.general';
module.exports = ErrorHandler;
;