UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

367 lines (332 loc) 11 kB
/** * 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) };