bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
283 lines (250 loc) • 8.71 kB
JavaScript
/**
* Centralized error registry and management
* Standardizes error handling across the application
* @class ErrorRegistry
*/
class ErrorRegistry {
/**
* Creates an instance of ErrorRegistry
* @param {Object} options - Options
* @param {Object} [options.logger] - Logger instance
*/
constructor(options = {}) {
this.logger = options.logger || console;
this.errorDefinitions = new Map();
this.errorStrategies = new Map();
this.errorHistory = [];
this.historyLimit = options.historyLimit || 100;
}
/**
* Register an error definition
* @param {String} code - Unique error code
* @param {Object} definition - Error definition
* @param {String} definition.message - Error message template
* @param {String} [definition.category='general'] - Error category
* @param {String} [definition.severity='error'] - Error severity (error|warning|info)
* @param {Boolean} [definition.isRecoverable=true] - If true, error can be recovered from
* @param {String} [definition.strategy] - Recovery strategy name
* @param {String} [definition.description] - Error description
* @returns {ErrorRegistry} - For method chaining
*/
registerError(code, definition) {
if (this.errorDefinitions.has(code)) {
this.logger.warn(`Error code '${code}' already registered, overwriting`);
}
const errorDef = {
code,
message: definition.message || `Error: ${code}`,
category: definition.category || 'general',
severity: definition.severity || 'error',
isRecoverable: definition.isRecoverable !== false,
strategy: definition.strategy || null,
description: definition.description || '',
...definition
};
this.errorDefinitions.set(code, errorDef);
return this;
}
/**
* Register a recovery strategy
* @param {String} name - Strategy name
* @param {Function} strategyFn - Strategy function
* @param {Object} [options={}] - Strategy options
* @returns {ErrorRegistry} - For method chaining
*/
registerStrategy(name, strategyFn, options = {}) {
if (typeof strategyFn !== 'function') {
throw new Error(`Error strategy must be a function: ${name}`);
}
this.errorStrategies.set(name, {
name,
execute: strategyFn,
options
});
return this;
}
/**
* Format error parameters in message template
* @param {String} template - Message template
* @param {Object} params - Parameters
* @returns {String} - Formatted message
* @private
*/
_formatMessage(template, params = {}) {
return template.replace(/\{(\w+)\}/g, (match, key) => {
return params[key] !== undefined ? String(params[key]) : match;
});
}
/**
* Create an error instance
* @param {String} code - Error code
* @param {Object} [params={}] - Error parameters
* @param {Error} [originalError] - Original error that caused this
* @returns {Error} - Error instance
*/
createError(code, params = {}, originalError = null) {
const definition = this.errorDefinitions.get(code);
if (!definition) {
this.logger.warn(`Unknown error code: ${code}, using generic error`);
const error = new Error(params.message || `Unknown error: ${code}`);
error.code = code;
error.params = params;
if (originalError) error.cause = originalError;
return error;
}
const message = this._formatMessage(definition.message, params);
const error = new Error(message);
// Add error metadata
error.code = code;
error.category = definition.category;
error.severity = definition.severity;
error.isRecoverable = definition.isRecoverable;
error.strategy = definition.strategy;
error.params = params;
if (originalError) error.cause = originalError;
// Add recovery method if strategy exists
if (definition.isRecoverable && definition.strategy) {
const strategy = this.errorStrategies.get(definition.strategy);
if (strategy) {
error.recover = async (context = {}) => {
return this.executeStrategy(definition.strategy, error, context);
};
}
}
// Record error in history
this._recordError(error);
return error;
}
/**
* Create and throw an error
* @param {String} code - Error code
* @param {Object} [params={}] - Error parameters
* @param {Error} [originalError] - Original error
* @throws {Error} - Throws the created error
*/
throwError(code, params = {}, originalError = null) {
throw this.createError(code, params, originalError);
}
/**
* Record error in history
* @param {Error} error - Error to record
* @private
*/
_recordError(error) {
this.errorHistory.unshift({
timestamp: new Date().toISOString(),
code: error.code,
message: error.message,
category: error.category,
severity: error.severity,
isRecoverable: error.isRecoverable,
strategy: error.strategy,
params: error.params,
stack: error.stack
});
// Limit history size
if (this.errorHistory.length > this.historyLimit) {
this.errorHistory.length = this.historyLimit;
}
}
/**
* Execute a recovery strategy
* @param {String} strategyName - Strategy name
* @param {Error} error - Error to recover from
* @param {Object} [context={}] - Recovery context
* @returns {Promise<Object>} - Recovery result
*/
async executeStrategy(strategyName, error, context = {}) {
const strategy = this.errorStrategies.get(strategyName);
if (!strategy) {
throw new Error(`Unknown recovery strategy: ${strategyName}`);
}
try {
const strategyContext = {
...context,
logger: this.logger,
registry: this
};
return await strategy.execute(error, strategyContext);
} catch (recoveryError) {
this.logger.error(`Error executing recovery strategy '${strategyName}':`, recoveryError);
throw new Error(`Recovery strategy '${strategyName}' failed: ${recoveryError.message}`);
}
}
/**
* Get error definition by code
* @param {String} code - Error code
* @returns {Object|null} - Error definition or null if not found
*/
getErrorDefinition(code) {
return this.errorDefinitions.get(code) || null;
}
/**
* Get strategy by name
* @param {String} name - Strategy name
* @returns {Object|null} - Strategy or null if not found
*/
getStrategy(name) {
return this.errorStrategies.get(name) || null;
}
/**
* Get all error definitions
* @returns {Map<String, Object>} - Map of error code to definition
*/
getAllErrorDefinitions() {
return new Map(this.errorDefinitions);
}
/**
* Get all recovery strategies
* @returns {Map<String, Object>} - Map of strategy name to strategy
*/
getAllStrategies() {
return new Map(this.errorStrategies);
}
/**
* Get error history
* @param {Object} [options={}] - Filter options
* @param {String} [options.category] - Filter by category
* @param {String} [options.severity] - Filter by severity
* @param {Number} [options.limit] - Limit number of results
* @returns {Array<Object>} - Error history
*/
getErrorHistory(options = {}) {
let history = [...this.errorHistory];
if (options.category) {
history = history.filter(e => e.category === options.category);
}
if (options.severity) {
history = history.filter(e => e.severity === options.severity);
}
if (options.limit) {
history = history.slice(0, options.limit);
}
return history;
}
/**
* Clear error history
* @returns {ErrorRegistry} - For method chaining
*/
clearHistory() {
this.errorHistory = [];
return this;
}
/**
* Wrap a function with error handling
* @param {Function} fn - Function to wrap
* @param {String} errorCode - Error code to use if function throws
* @param {Object} [params={}] - Additional error parameters
* @returns {Function} - Wrapped function
*/
withErrorHandling(fn, errorCode, params = {}) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
throw this.createError(errorCode, { ...params, originalMessage: error.message }, error);
}
};
}
}
module.exports = ErrorRegistry;