bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
367 lines (332 loc) • 11 kB
JavaScript
/**
* Error Handler
*
* Provides a centralized error handling system for the bowling analysis system.
* Includes custom error classes and error handling utilities.
*/
const { defaultLogger } = require('./logger');
/**
* Base error class for all bowling analysis errors
*/
class BowlingAnalysisError extends Error {
/**
* Create a new BowlingAnalysisError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
*/
constructor(message, options = {}) {
super(message);
this.name = this.constructor.name;
this.code = options.code || 'ERR_BOWLING_ANALYSIS';
this.cause = options.cause;
this.details = options.details || {};
this.timestamp = new Date();
// Capture stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
/**
* Convert error to JSON
* @returns {Object} JSON representation of the error
*/
toJSON() {
return {
name: this.name,
message: this.message,
code: this.code,
details: this.details,
timestamp: this.timestamp.toISOString(),
stack: this.stack,
cause: this.cause ? (this.cause.toJSON ? this.cause.toJSON() : this.cause.message) : undefined
};
}
/**
* Get a formatted string representation of the error
* @returns {string} Formatted error string
*/
toString() {
let result = `${this.name}: ${this.message} [${this.code}]`;
if (Object.keys(this.details).length > 0) {
result += `\nDetails: ${JSON.stringify(this.details, null, 2)}`;
}
if (this.cause) {
result += `\nCaused by: ${this.cause.toString()}`;
}
return result;
}
}
/**
* Error thrown when validation fails
*/
class ValidationError extends BowlingAnalysisError {
/**
* Create a new ValidationError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
* @param {Array} [options.validationErrors] - Validation errors
*/
constructor(message, options = {}) {
super(message, {
...options,
code: options.code || 'ERR_VALIDATION'
});
this.validationErrors = options.validationErrors || [];
this.details = {
...this.details,
validationErrors: this.validationErrors
};
}
}
/**
* Error thrown when a file operation fails
*/
class FileError extends BowlingAnalysisError {
/**
* Create a new FileError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
* @param {string} [options.filePath] - Path of the file that caused the error
* @param {string} [options.operation] - File operation that failed
*/
constructor(message, options = {}) {
super(message, {
...options,
code: options.code || 'ERR_FILE'
});
this.filePath = options.filePath;
this.operation = options.operation;
this.details = {
...this.details,
filePath: this.filePath,
operation: this.operation
};
}
}
/**
* Error thrown when a processor fails
*/
class ProcessorError extends BowlingAnalysisError {
/**
* Create a new ProcessorError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
* @param {string} [options.processorName] - Name of the processor that failed
* @param {string} [options.stage] - Processing stage that failed
*/
constructor(message, options = {}) {
super(message, {
...options,
code: options.code || 'ERR_PROCESSOR'
});
this.processorName = options.processorName;
this.stage = options.stage;
this.details = {
...this.details,
processorName: this.processorName,
stage: this.stage
};
}
}
/**
* Error thrown when a configuration error occurs
*/
class ConfigurationError extends BowlingAnalysisError {
/**
* Create a new ConfigurationError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
* @param {string} [options.configKey] - Configuration key that caused the error
*/
constructor(message, options = {}) {
super(message, {
...options,
code: options.code || 'ERR_CONFIGURATION'
});
this.configKey = options.configKey;
this.details = {
...this.details,
configKey: this.configKey
};
}
}
/**
* Error thrown when a pipeline error occurs
*/
class PipelineError extends BowlingAnalysisError {
/**
* Create a new PipelineError
* @param {string} message - Error message
* @param {Object} [options] - Error options
* @param {string} [options.code] - Error code
* @param {Error} [options.cause] - Underlying cause of the error
* @param {Object} [options.details] - Additional error details
* @param {string} [options.pipelineName] - Name of the pipeline that failed
* @param {string} [options.processorName] - Name of the processor that failed
*/
constructor(message, options = {}) {
super(message, {
...options,
code: options.code || 'ERR_PIPELINE'
});
this.pipelineName = options.pipelineName;
this.processorName = options.processorName;
this.details = {
...this.details,
pipelineName: this.pipelineName,
processorName: this.processorName
};
}
}
/**
* Error handler class for centralized error handling
*/
class ErrorHandler {
/**
* Create a new ErrorHandler
* @param {Object} [options] - Error handler options
* @param {Object} [options.logger] - Logger instance
* @param {Function} [options.onError] - Error callback
* @param {boolean} [options.exitOnFatalError] - Whether to exit the process on fatal errors
*/
constructor(options = {}) {
this.logger = options.logger || defaultLogger;
this.onError = options.onError;
this.exitOnFatalError = options.exitOnFatalError !== false;
}
/**
* Handle an error
* @param {Error} error - Error to handle
* @param {Object} [options] - Handling options
* @param {boolean} [options.isFatal] - Whether the error is fatal
* @param {string} [options.source] - Source of the error
* @param {Object} [options.context] - Additional context
* @returns {Error} The handled error
*/
handleError(error, options = {}) {
const isFatal = options.isFatal === true;
const source = options.source || 'unknown';
const context = options.context || {};
// Convert to BowlingAnalysisError if it's not already
const bowlingError = this._convertError(error, source, context);
// Log the error
if (isFatal) {
this.logger.fatal(`Fatal error in ${source}: ${bowlingError.message}`, { error: bowlingError });
} else {
this.logger.error(`Error in ${source}: ${bowlingError.message}`, { error: bowlingError });
}
// Call error callback if provided
if (typeof this.onError === 'function') {
try {
this.onError(bowlingError, { isFatal, source, context });
} catch (callbackError) {
this.logger.error(`Error in error callback: ${callbackError.message}`);
}
}
// Exit process on fatal error if configured
if (isFatal && this.exitOnFatalError) {
process.exit(1);
}
return bowlingError;
}
/**
* Convert an error to a BowlingAnalysisError
* @private
* @param {Error} error - Error to convert
* @param {string} source - Source of the error
* @param {Object} context - Additional context
* @returns {BowlingAnalysisError} Converted error
*/
_convertError(error, source, context) {
if (error instanceof BowlingAnalysisError) {
return error;
}
return new BowlingAnalysisError(error.message, {
cause: error,
details: {
source,
context,
originalStack: error.stack
}
});
}
/**
* Create an async error handler function
* @param {Function} fn - Async function to wrap
* @param {Object} [options] - Handling options
* @param {boolean} [options.isFatal] - Whether errors are fatal
* @param {string} [options.source] - Source of errors
* @returns {Function} Wrapped function
*/
wrapAsync(fn, options = {}) {
const handler = this;
return async function(...args) {
try {
return await fn.apply(this, args);
} catch (error) {
return handler.handleError(error, {
...options,
context: { args }
});
}
};
}
/**
* Register global error handlers
* @param {Object} [options] - Registration options
* @param {boolean} [options.unhandledRejections] - Whether to handle unhandled rejections
* @param {boolean} [options.uncaughtExceptions] - Whether to handle uncaught exceptions
*/
registerGlobalHandlers(options = {}) {
const handleUnhandledRejections = options.unhandledRejections !== false;
const handleUncaughtExceptions = options.uncaughtExceptions !== false;
if (handleUnhandledRejections) {
process.on('unhandledRejection', (reason, promise) => {
this.handleError(reason instanceof Error ? reason : new Error(String(reason)), {
isFatal: true,
source: 'unhandledRejection',
context: { promise }
});
});
}
if (handleUncaughtExceptions) {
process.on('uncaughtException', (error) => {
this.handleError(error, {
isFatal: true,
source: 'uncaughtException'
});
});
}
}
}
// Create default error handler instance
const defaultErrorHandler = new ErrorHandler();
module.exports = {
BowlingAnalysisError,
ValidationError,
FileError,
ProcessorError,
ConfigurationError,
PipelineError,
ErrorHandler,
defaultErrorHandler,
// Export convenience methods that use the default error handler
handleError: (...args) => defaultErrorHandler.handleError(...args),
wrapAsync: (...args) => defaultErrorHandler.wrapAsync(...args),
registerGlobalHandlers: (...args) => defaultErrorHandler.registerGlobalHandlers(...args)
};