437 lines
9.9 KiB
TypeScript
437 lines
9.9 KiB
TypeScript
|
import { ErrorSeverity, ErrorCategory, ErrorRecoverability } from './error.codes.js';
|
||
|
import { logger } from '../logger.js';
|
||
|
|
||
|
// Import TLogLevel from plugins
|
||
|
import type { TLogLevel } from '../plugins.js';
|
||
|
|
||
|
/**
|
||
|
* Context information added to structured errors
|
||
|
*/
|
||
|
export interface IErrorContext {
|
||
|
/** Component or service where the error occurred */
|
||
|
component?: string;
|
||
|
|
||
|
/** Operation that was being performed */
|
||
|
operation?: string;
|
||
|
|
||
|
/** Unique request ID if available */
|
||
|
requestId?: string;
|
||
|
|
||
|
/** Error occurred at timestamp */
|
||
|
timestamp?: number;
|
||
|
|
||
|
/** User-visible message (safe to display to end-users) */
|
||
|
userMessage?: string;
|
||
|
|
||
|
/** Additional structured data for debugging */
|
||
|
data?: Record<string, any>;
|
||
|
|
||
|
/** Related entity IDs if applicable */
|
||
|
entity?: {
|
||
|
type: string;
|
||
|
id: string | number;
|
||
|
};
|
||
|
|
||
|
/** Stack trace (if enabled in configuration) */
|
||
|
stack?: string;
|
||
|
|
||
|
/** Retry information if applicable */
|
||
|
retry?: {
|
||
|
/** Maximum number of retries allowed */
|
||
|
maxRetries?: number;
|
||
|
|
||
|
/** Current retry count */
|
||
|
currentRetry?: number;
|
||
|
|
||
|
/** Next retry timestamp */
|
||
|
nextRetryAt?: number;
|
||
|
|
||
|
/** Delay between retries (in ms) */
|
||
|
retryDelay?: number;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Base class for all errors in the Platform Service
|
||
|
* Adds structured error information, logging, and error tracking
|
||
|
*/
|
||
|
export class PlatformError extends Error {
|
||
|
/** Error code identifying the specific error type */
|
||
|
public readonly code: string;
|
||
|
|
||
|
/** Error severity level */
|
||
|
public readonly severity: ErrorSeverity;
|
||
|
|
||
|
/** Error category for grouping related errors */
|
||
|
public readonly category: ErrorCategory;
|
||
|
|
||
|
/** Whether the error can be recovered from automatically */
|
||
|
public readonly recoverability: ErrorRecoverability;
|
||
|
|
||
|
/** Additional context information */
|
||
|
public readonly context: IErrorContext;
|
||
|
|
||
|
/**
|
||
|
* Creates a new PlatformError
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code from error.codes.ts
|
||
|
* @param severity Error severity level
|
||
|
* @param category Error category
|
||
|
* @param recoverability Error recoverability indication
|
||
|
* @param context Additional context information
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
severity: ErrorSeverity = ErrorSeverity.MEDIUM,
|
||
|
category: ErrorCategory = ErrorCategory.OTHER,
|
||
|
recoverability: ErrorRecoverability = ErrorRecoverability.NON_RECOVERABLE,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(message);
|
||
|
|
||
|
// Set error metadata
|
||
|
this.name = this.constructor.name;
|
||
|
this.code = code;
|
||
|
this.severity = severity;
|
||
|
this.category = category;
|
||
|
this.recoverability = recoverability;
|
||
|
|
||
|
// Add timestamp if not provided
|
||
|
this.context = {
|
||
|
...context,
|
||
|
timestamp: context.timestamp || Date.now(),
|
||
|
};
|
||
|
|
||
|
// Capture stack trace
|
||
|
Error.captureStackTrace(this, this.constructor);
|
||
|
|
||
|
// Log the error automatically unless explicitly disabled
|
||
|
if (!context.data?.skipLogging) {
|
||
|
this.logError();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Logs the error using the platform logger
|
||
|
*/
|
||
|
private logError(): void {
|
||
|
const logLevel = this.getLogLevelFromSeverity() as TLogLevel;
|
||
|
|
||
|
// Construct structured log entry
|
||
|
const logData = {
|
||
|
error_code: this.code,
|
||
|
error_name: this.name,
|
||
|
severity: this.severity,
|
||
|
category: this.category,
|
||
|
recoverability: this.recoverability,
|
||
|
...this.context
|
||
|
};
|
||
|
|
||
|
// Log with appropriate level
|
||
|
logger.log(logLevel, this.message, logData);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Maps severity levels to log levels
|
||
|
*/
|
||
|
private getLogLevelFromSeverity(): string {
|
||
|
switch (this.severity) {
|
||
|
case ErrorSeverity.CRITICAL:
|
||
|
case ErrorSeverity.HIGH:
|
||
|
return 'error';
|
||
|
case ErrorSeverity.MEDIUM:
|
||
|
return 'warn';
|
||
|
case ErrorSeverity.LOW:
|
||
|
return 'info';
|
||
|
case ErrorSeverity.INFO:
|
||
|
return 'debug';
|
||
|
default:
|
||
|
return 'error';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a JSON representation of the error
|
||
|
*/
|
||
|
public toJSON(): Record<string, any> {
|
||
|
return {
|
||
|
name: this.name,
|
||
|
message: this.message,
|
||
|
code: this.code,
|
||
|
severity: this.severity,
|
||
|
category: this.category,
|
||
|
recoverability: this.recoverability,
|
||
|
context: this.context,
|
||
|
stack: process.env.NODE_ENV !== 'production' ? this.stack : undefined
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an instance with retry information
|
||
|
*
|
||
|
* @param maxRetries Maximum number of retries
|
||
|
* @param currentRetry Current retry count
|
||
|
* @param retryDelay Delay between retries in ms
|
||
|
*/
|
||
|
public withRetry(
|
||
|
maxRetries: number,
|
||
|
currentRetry: number = 0,
|
||
|
retryDelay: number = 1000
|
||
|
): PlatformError {
|
||
|
const nextRetryAt = Date.now() + retryDelay;
|
||
|
|
||
|
// Create a new instance with the same parameters but updated context
|
||
|
return new (this.constructor as typeof PlatformError)(
|
||
|
this.message,
|
||
|
this.code,
|
||
|
this.severity,
|
||
|
this.category,
|
||
|
// If we can retry, the error is at least maybe recoverable
|
||
|
currentRetry < maxRetries
|
||
|
? ErrorRecoverability.MAYBE_RECOVERABLE
|
||
|
: this.recoverability,
|
||
|
{
|
||
|
...this.context,
|
||
|
retry: {
|
||
|
maxRetries,
|
||
|
currentRetry,
|
||
|
nextRetryAt,
|
||
|
retryDelay
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if the error should be retried based on retry information
|
||
|
*/
|
||
|
public shouldRetry(): boolean {
|
||
|
const { retry } = this.context;
|
||
|
if (!retry) return false;
|
||
|
|
||
|
return retry.currentRetry < retry.maxRetries;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a user-friendly message that is safe to display to end users
|
||
|
*/
|
||
|
public getUserMessage(): string {
|
||
|
return this.context.userMessage || 'An unexpected error occurred.';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for validation errors
|
||
|
*/
|
||
|
export class ValidationError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new validation error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.LOW,
|
||
|
ErrorCategory.VALIDATION,
|
||
|
ErrorRecoverability.NON_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for configuration errors
|
||
|
*/
|
||
|
export class ConfigurationError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new configuration error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.MEDIUM,
|
||
|
ErrorCategory.CONFIGURATION,
|
||
|
ErrorRecoverability.NON_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for network-related errors
|
||
|
*/
|
||
|
export class NetworkError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new network error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.MEDIUM,
|
||
|
ErrorCategory.CONNECTIVITY,
|
||
|
ErrorRecoverability.MAYBE_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for resource availability errors (rate limits, quotas)
|
||
|
*/
|
||
|
export class ResourceError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new resource error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.MEDIUM,
|
||
|
ErrorCategory.RESOURCE,
|
||
|
ErrorRecoverability.MAYBE_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for authentication/authorization errors
|
||
|
*/
|
||
|
export class AuthenticationError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new authentication error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.HIGH,
|
||
|
ErrorCategory.AUTHENTICATION,
|
||
|
ErrorRecoverability.NON_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for operation errors (API calls, processing)
|
||
|
*/
|
||
|
export class OperationError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new operation error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.MEDIUM,
|
||
|
ErrorCategory.OPERATION,
|
||
|
ErrorRecoverability.MAYBE_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Error class for critical system errors
|
||
|
*/
|
||
|
export class SystemError extends PlatformError {
|
||
|
/**
|
||
|
* Creates a new system error
|
||
|
*
|
||
|
* @param message Error message
|
||
|
* @param code Error code
|
||
|
* @param context Additional context
|
||
|
*/
|
||
|
constructor(
|
||
|
message: string,
|
||
|
code: string,
|
||
|
context: IErrorContext = {}
|
||
|
) {
|
||
|
super(
|
||
|
message,
|
||
|
code,
|
||
|
ErrorSeverity.CRITICAL,
|
||
|
ErrorCategory.OTHER,
|
||
|
ErrorRecoverability.NON_RECOVERABLE,
|
||
|
context
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper to get the appropriate error class based on error category
|
||
|
*
|
||
|
* @param category Error category
|
||
|
* @returns The appropriate error class
|
||
|
*/
|
||
|
export function getErrorClassForCategory(category: ErrorCategory): any {
|
||
|
switch (category) {
|
||
|
case ErrorCategory.VALIDATION:
|
||
|
return ValidationError;
|
||
|
case ErrorCategory.CONFIGURATION:
|
||
|
return ConfigurationError;
|
||
|
case ErrorCategory.CONNECTIVITY:
|
||
|
return NetworkError;
|
||
|
case ErrorCategory.RESOURCE:
|
||
|
return ResourceError;
|
||
|
case ErrorCategory.AUTHENTICATION:
|
||
|
return AuthenticationError;
|
||
|
case ErrorCategory.OPERATION:
|
||
|
return OperationError;
|
||
|
default:
|
||
|
return PlatformError;
|
||
|
}
|
||
|
}
|