update
This commit is contained in:
437
ts/errors/base.errors.ts
Normal file
437
ts/errors/base.errors.ts
Normal file
@ -0,0 +1,437 @@
|
||||
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;
|
||||
}
|
||||
}
|
313
ts/errors/email.errors.ts
Normal file
313
ts/errors/email.errors.ts
Normal file
@ -0,0 +1,313 @@
|
||||
import {
|
||||
PlatformError,
|
||||
ValidationError,
|
||||
NetworkError,
|
||||
ResourceError,
|
||||
OperationError
|
||||
} from './base.errors.js';
|
||||
import type { IErrorContext } from './base.errors.js';
|
||||
|
||||
import {
|
||||
EMAIL_SERVICE_ERROR,
|
||||
EMAIL_TEMPLATE_ERROR,
|
||||
EMAIL_VALIDATION_ERROR,
|
||||
EMAIL_SEND_ERROR,
|
||||
EMAIL_RECEIVE_ERROR,
|
||||
EMAIL_ATTACHMENT_ERROR,
|
||||
EMAIL_PARSE_ERROR,
|
||||
EMAIL_RATE_LIMIT_EXCEEDED
|
||||
} from './error.codes.js';
|
||||
|
||||
/**
|
||||
* Base class for all email service related errors
|
||||
*/
|
||||
export class EmailServiceError extends OperationError {
|
||||
/**
|
||||
* Creates a new email service error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_SERVICE_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email template errors
|
||||
*/
|
||||
export class EmailTemplateError extends OperationError {
|
||||
/**
|
||||
* Creates a new email template error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_TEMPLATE_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email validation errors
|
||||
*/
|
||||
export class EmailValidationError extends ValidationError {
|
||||
/**
|
||||
* Creates a new email validation error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_VALIDATION_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email sending errors
|
||||
*/
|
||||
export class EmailSendError extends OperationError {
|
||||
/**
|
||||
* Creates a new email send error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_SEND_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a permanently failed send
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static permanent(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
): EmailSendError {
|
||||
return new EmailSendError(`Permanent send failure: ${message}`, {
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
permanent: true
|
||||
},
|
||||
userMessage: 'The email could not be delivered due to a permanent failure.'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a temporary failed send
|
||||
*
|
||||
* @param message Error message
|
||||
* @param maxRetries Maximum number of retries
|
||||
* @param currentRetry Current retry count
|
||||
* @param retryDelay Delay between retries in ms
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static temporary(
|
||||
message: string,
|
||||
maxRetries: number = 3,
|
||||
currentRetry: number = 0,
|
||||
retryDelay: number = 60000,
|
||||
context: IErrorContext = {}
|
||||
): EmailSendError {
|
||||
const error = new EmailSendError(`Temporary send failure: ${message}`, {
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
permanent: false
|
||||
},
|
||||
userMessage: 'The email delivery failed temporarily. It will be retried.'
|
||||
});
|
||||
|
||||
return error.withRetry(maxRetries, currentRetry, retryDelay) as EmailSendError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a permanent send failure
|
||||
*/
|
||||
public isPermanent(): boolean {
|
||||
return !!this.context.data?.permanent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email receiving errors
|
||||
*/
|
||||
export class EmailReceiveError extends OperationError {
|
||||
/**
|
||||
* Creates a new email receive error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_RECEIVE_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email attachment errors
|
||||
*/
|
||||
export class EmailAttachmentError extends ValidationError {
|
||||
/**
|
||||
* Creates a new email attachment error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_ATTACHMENT_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an attachment too large error
|
||||
*
|
||||
* @param size Attachment size in bytes
|
||||
* @param maxSize Maximum allowed size in bytes
|
||||
* @param filename Attachment filename
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static tooLarge(
|
||||
size: number,
|
||||
maxSize: number,
|
||||
filename?: string,
|
||||
context: IErrorContext = {}
|
||||
): EmailAttachmentError {
|
||||
const filenameText = filename ? ` (${filename})` : '';
|
||||
return new EmailAttachmentError(
|
||||
`Attachment${filenameText} size ${size} bytes exceeds maximum allowed size of ${maxSize} bytes`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
size,
|
||||
maxSize,
|
||||
filename
|
||||
},
|
||||
userMessage: `The attachment${filenameText} is too large. Maximum size is ${Math.round(maxSize / 1024 / 1024)} MB.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an invalid attachment type error
|
||||
*
|
||||
* @param contentType Attachment content type
|
||||
* @param filename Attachment filename
|
||||
* @param allowedTypes List of allowed content types
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static invalidType(
|
||||
contentType: string,
|
||||
filename: string,
|
||||
allowedTypes: string[],
|
||||
context: IErrorContext = {}
|
||||
): EmailAttachmentError {
|
||||
return new EmailAttachmentError(
|
||||
`Attachment '${filename}' with content type '${contentType}' is not allowed. Allowed types: ${allowedTypes.join(', ')}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
contentType,
|
||||
filename,
|
||||
allowedTypes
|
||||
},
|
||||
userMessage: `The attachment type ${contentType} is not allowed.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email parsing errors
|
||||
*/
|
||||
export class EmailParseError extends OperationError {
|
||||
/**
|
||||
* Creates a new email parse error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_PARSE_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for email rate limit exceeded errors
|
||||
*/
|
||||
export class EmailRateLimitError extends ResourceError {
|
||||
/**
|
||||
* Creates a new email rate limit error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, EMAIL_RATE_LIMIT_EXCEEDED, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with rate limit information
|
||||
*
|
||||
* @param limit Rate limit
|
||||
* @param remaining Remaining quota
|
||||
* @param resetAt Time when the quota resets
|
||||
* @param scope Rate limit scope (global, domain, user, etc.)
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static withLimitInfo(
|
||||
limit: number,
|
||||
remaining: number,
|
||||
resetAt: Date | number,
|
||||
scope: string = 'global',
|
||||
context: IErrorContext = {}
|
||||
): EmailRateLimitError {
|
||||
const resetTime = typeof resetAt === 'number' ? new Date(resetAt) : resetAt;
|
||||
const resetTimeStr = resetTime.toISOString();
|
||||
|
||||
return new EmailRateLimitError(
|
||||
`Email rate limit exceeded: ${remaining}/${limit} remaining in ${scope} scope, resets at ${resetTimeStr}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
limit,
|
||||
remaining,
|
||||
resetAt: resetTime.getTime(),
|
||||
resetTimeStr,
|
||||
scope
|
||||
},
|
||||
userMessage: `You've reached the email sending limit. Please try again later.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
412
ts/errors/error-handler.ts
Normal file
412
ts/errors/error-handler.ts
Normal file
@ -0,0 +1,412 @@
|
||||
import { PlatformError } from './base.errors.js';
|
||||
import type { IErrorContext } from './base.errors.js';
|
||||
import { ErrorCategory, ErrorRecoverability, ErrorSeverity } from './error.codes.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
/**
|
||||
* Error handler configuration
|
||||
*/
|
||||
export interface IErrorHandlerConfig {
|
||||
/** Whether to log errors automatically */
|
||||
logErrors: boolean;
|
||||
|
||||
/** Whether to include stack traces in prod environment */
|
||||
includeStacksInProd: boolean;
|
||||
|
||||
/** Default retry options */
|
||||
retry: {
|
||||
/** Maximum retry attempts */
|
||||
maxAttempts: number;
|
||||
|
||||
/** Base delay between retries in ms */
|
||||
baseDelay: number;
|
||||
|
||||
/** Maximum delay between retries in ms */
|
||||
maxDelay: number;
|
||||
|
||||
/** Backoff factor for exponential backoff */
|
||||
backoffFactor: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Global error handler configuration
|
||||
*/
|
||||
const config: IErrorHandlerConfig = {
|
||||
logErrors: true,
|
||||
includeStacksInProd: false,
|
||||
retry: {
|
||||
maxAttempts: 3,
|
||||
baseDelay: 1000,
|
||||
maxDelay: 30000,
|
||||
backoffFactor: 2
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Error handler utility
|
||||
* Provides methods for consistent error handling across the platform
|
||||
*/
|
||||
export class ErrorHandler {
|
||||
/**
|
||||
* Current configuration
|
||||
*/
|
||||
public static config = config;
|
||||
|
||||
/**
|
||||
* Update error handler configuration
|
||||
*
|
||||
* @param newConfig New configuration (partial)
|
||||
*/
|
||||
public static configure(newConfig: Partial<IErrorHandlerConfig>): void {
|
||||
ErrorHandler.config = {
|
||||
...ErrorHandler.config,
|
||||
...newConfig,
|
||||
retry: {
|
||||
...ErrorHandler.config.retry,
|
||||
...(newConfig.retry || {})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any error to a PlatformError
|
||||
*
|
||||
* @param error Error to convert
|
||||
* @param defaultCode Default error code if not a PlatformError
|
||||
* @param context Additional context
|
||||
* @returns PlatformError instance
|
||||
*/
|
||||
public static toPlatformError(
|
||||
error: any,
|
||||
defaultCode: string,
|
||||
context: IErrorContext = {}
|
||||
): PlatformError {
|
||||
// If already a PlatformError, just add context
|
||||
if (error instanceof PlatformError) {
|
||||
// Add context if provided
|
||||
if (Object.keys(context).length > 0) {
|
||||
return new (error.constructor as typeof PlatformError)(
|
||||
error.message,
|
||||
error.code,
|
||||
error.severity,
|
||||
error.category,
|
||||
error.recoverability,
|
||||
{
|
||||
...error.context,
|
||||
...context,
|
||||
data: {
|
||||
...(error.context.data || {}),
|
||||
...(context.data || {})
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
// Convert standard Error to PlatformError
|
||||
if (error instanceof Error) {
|
||||
return new PlatformError(
|
||||
error.message,
|
||||
defaultCode,
|
||||
ErrorSeverity.MEDIUM,
|
||||
ErrorCategory.OPERATION,
|
||||
ErrorRecoverability.NON_RECOVERABLE,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...(context.data || {}),
|
||||
originalError: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Not an Error instance
|
||||
return new PlatformError(
|
||||
typeof error === 'string' ? error : 'Unknown error',
|
||||
defaultCode,
|
||||
ErrorSeverity.MEDIUM,
|
||||
ErrorCategory.OPERATION,
|
||||
ErrorRecoverability.NON_RECOVERABLE,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an error for API responses
|
||||
* Sanitizes errors for safe external exposure
|
||||
*
|
||||
* @param error Error to format
|
||||
* @param includeDetails Whether to include detailed information
|
||||
* @returns Formatted error object
|
||||
*/
|
||||
public static formatErrorForResponse(
|
||||
error: any,
|
||||
includeDetails: boolean = false
|
||||
): Record<string, any> {
|
||||
const platformError = ErrorHandler.toPlatformError(
|
||||
error,
|
||||
'PLATFORM_OPERATION_ERROR'
|
||||
);
|
||||
|
||||
// Basic error information
|
||||
const responseError: Record<string, any> = {
|
||||
code: platformError.code,
|
||||
message: platformError.getUserMessage(),
|
||||
requestId: platformError.context.requestId
|
||||
};
|
||||
|
||||
// Include more details if requested
|
||||
if (includeDetails) {
|
||||
responseError.details = {
|
||||
severity: platformError.severity,
|
||||
category: platformError.category,
|
||||
rawMessage: platformError.message,
|
||||
data: platformError.context.data
|
||||
};
|
||||
|
||||
// Only include stack trace in non-production or if explicitly enabled
|
||||
if (process.env.NODE_ENV !== 'production' || ErrorHandler.config.includeStacksInProd) {
|
||||
responseError.details.stack = platformError.stack;
|
||||
}
|
||||
}
|
||||
|
||||
return responseError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an error with consistent logging and formatting
|
||||
*
|
||||
* @param error Error to handle
|
||||
* @param defaultCode Default error code if not a PlatformError
|
||||
* @param context Additional context
|
||||
* @returns Formatted error for response
|
||||
*/
|
||||
public static handleError(
|
||||
error: any,
|
||||
defaultCode: string,
|
||||
context: IErrorContext = {}
|
||||
): Record<string, any> {
|
||||
const platformError = ErrorHandler.toPlatformError(
|
||||
error,
|
||||
defaultCode,
|
||||
context
|
||||
);
|
||||
|
||||
// Log the error if enabled
|
||||
if (ErrorHandler.config.logErrors) {
|
||||
logger.error(platformError.message, {
|
||||
error_code: platformError.code,
|
||||
error_name: platformError.name,
|
||||
error_severity: platformError.severity,
|
||||
error_category: platformError.category,
|
||||
error_recoverability: platformError.recoverability,
|
||||
...platformError.context,
|
||||
stack: platformError.stack
|
||||
});
|
||||
}
|
||||
|
||||
// Return formatted error for response
|
||||
const isDetailedMode = process.env.NODE_ENV !== 'production';
|
||||
return ErrorHandler.formatErrorForResponse(platformError, isDetailedMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function with error handling
|
||||
*
|
||||
* @param fn Function to execute
|
||||
* @param defaultCode Default error code if the function throws
|
||||
* @param context Additional context
|
||||
* @returns Function result or error
|
||||
*/
|
||||
public static async execute<T>(
|
||||
fn: () => Promise<T>,
|
||||
defaultCode: string,
|
||||
context: IErrorContext = {}
|
||||
): Promise<T> {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
throw ErrorHandler.toPlatformError(error, defaultCode, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function with retries and exponential backoff
|
||||
*
|
||||
* @param fn Function to execute
|
||||
* @param defaultCode Default error code if the function throws
|
||||
* @param options Retry options
|
||||
* @param context Additional context
|
||||
* @returns Function result or error after max retries
|
||||
*/
|
||||
public static async executeWithRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
defaultCode: string,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
baseDelay?: number;
|
||||
maxDelay?: number;
|
||||
backoffFactor?: number;
|
||||
retryableErrorCodes?: string[];
|
||||
retryableErrorPatterns?: RegExp[];
|
||||
onRetry?: (error: PlatformError, attempt: number, delay: number) => void;
|
||||
} = {},
|
||||
context: IErrorContext = {}
|
||||
): Promise<T> {
|
||||
const {
|
||||
maxAttempts = ErrorHandler.config.retry.maxAttempts,
|
||||
baseDelay = ErrorHandler.config.retry.baseDelay,
|
||||
maxDelay = ErrorHandler.config.retry.maxDelay,
|
||||
backoffFactor = ErrorHandler.config.retry.backoffFactor,
|
||||
retryableErrorCodes = [],
|
||||
retryableErrorPatterns = [],
|
||||
onRetry = () => {}
|
||||
} = options;
|
||||
|
||||
let lastError: PlatformError;
|
||||
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
// Convert to PlatformError
|
||||
const platformError = ErrorHandler.toPlatformError(
|
||||
error,
|
||||
defaultCode,
|
||||
{
|
||||
...context,
|
||||
retry: {
|
||||
currentRetry: attempt,
|
||||
maxRetries: maxAttempts,
|
||||
nextRetryAt: 0 // Will be set below if retrying
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
lastError = platformError;
|
||||
|
||||
// Check if we should retry
|
||||
const isLastAttempt = attempt >= maxAttempts - 1;
|
||||
|
||||
if (isLastAttempt) {
|
||||
// No more retries
|
||||
throw platformError;
|
||||
}
|
||||
|
||||
// Check if error is retryable
|
||||
const isRetryable =
|
||||
// Built-in recoverability
|
||||
platformError.recoverability === ErrorRecoverability.RECOVERABLE ||
|
||||
platformError.recoverability === ErrorRecoverability.MAYBE_RECOVERABLE ||
|
||||
platformError.recoverability === ErrorRecoverability.TRANSIENT ||
|
||||
// Specifically included error codes
|
||||
retryableErrorCodes.includes(platformError.code) ||
|
||||
// Matches error message patterns
|
||||
retryableErrorPatterns.some(pattern => pattern.test(platformError.message));
|
||||
|
||||
if (!isRetryable) {
|
||||
throw platformError;
|
||||
}
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
const delay = Math.min(baseDelay * Math.pow(backoffFactor, attempt), maxDelay);
|
||||
|
||||
// Add jitter to prevent thundering herd problem (±20%)
|
||||
const jitter = 0.8 + Math.random() * 0.4;
|
||||
const actualDelay = Math.floor(delay * jitter);
|
||||
|
||||
// Update nextRetryAt in error context
|
||||
const nextRetryAt = Date.now() + actualDelay;
|
||||
platformError.context.retry!.nextRetryAt = nextRetryAt;
|
||||
|
||||
// Log retry attempt
|
||||
logger.warn(`Retrying operation after error (attempt ${attempt + 1}/${maxAttempts}): ${platformError.message}`, {
|
||||
error_code: platformError.code,
|
||||
retry_attempt: attempt + 1,
|
||||
retry_max_attempts: maxAttempts,
|
||||
retry_delay_ms: actualDelay,
|
||||
retry_next_at: new Date(nextRetryAt).toISOString()
|
||||
});
|
||||
|
||||
// Call onRetry callback
|
||||
onRetry(platformError, attempt + 1, actualDelay);
|
||||
|
||||
// Wait before next retry
|
||||
await new Promise(resolve => setTimeout(resolve, actualDelay));
|
||||
}
|
||||
}
|
||||
|
||||
// This should never happen, but TypeScript needs it
|
||||
throw lastError!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a middleware for handling errors in HTTP requests
|
||||
*
|
||||
* @returns Middleware function
|
||||
*/
|
||||
export function createErrorHandlerMiddleware() {
|
||||
return (error: any, req: any, res: any, next: any) => {
|
||||
// Add request context
|
||||
const context: IErrorContext = {
|
||||
requestId: req.headers['x-request-id'] || req.headers['x-correlation-id'],
|
||||
component: 'HttpServer',
|
||||
operation: `${req.method} ${req.url}`,
|
||||
data: {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
query: req.query,
|
||||
params: req.params,
|
||||
ip: req.ip || req.connection.remoteAddress,
|
||||
userAgent: req.headers['user-agent']
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the error
|
||||
const formattedError = ErrorHandler.handleError(
|
||||
error,
|
||||
'PLATFORM_OPERATION_ERROR',
|
||||
context
|
||||
);
|
||||
|
||||
// Set status code based on error type
|
||||
let statusCode = 500;
|
||||
|
||||
if (error instanceof PlatformError) {
|
||||
// Map error categories to HTTP status codes
|
||||
switch (error.category) {
|
||||
case ErrorCategory.VALIDATION:
|
||||
statusCode = 400;
|
||||
break;
|
||||
case ErrorCategory.AUTHENTICATION:
|
||||
statusCode = 401;
|
||||
break;
|
||||
case ErrorCategory.RESOURCE:
|
||||
statusCode = 429;
|
||||
break;
|
||||
case ErrorCategory.OPERATION:
|
||||
statusCode = 400;
|
||||
break;
|
||||
default:
|
||||
statusCode = 500;
|
||||
}
|
||||
} else if (error.statusCode) {
|
||||
// Use provided status code if available
|
||||
statusCode = error.statusCode;
|
||||
}
|
||||
|
||||
// Send error response
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: formattedError
|
||||
});
|
||||
};
|
||||
}
|
193
ts/errors/index.ts
Normal file
193
ts/errors/index.ts
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Platform Service Error System
|
||||
*
|
||||
* This module provides a comprehensive error handling system for the Platform Service,
|
||||
* with structured error types, error codes, and consistent patterns for logging and recovery.
|
||||
*/
|
||||
|
||||
// Export error codes and types
|
||||
export * from './error.codes.js';
|
||||
|
||||
// Export base error classes
|
||||
export * from './base.errors.js';
|
||||
|
||||
// Export domain-specific error classes
|
||||
export * from './email.errors.js';
|
||||
export * from './mta.errors.js';
|
||||
export * from './reputation.errors.js';
|
||||
|
||||
// Export utility function to create specific error types based on the error category
|
||||
import { getErrorClassForCategory } from './base.errors.js';
|
||||
export { getErrorClassForCategory };
|
||||
|
||||
/**
|
||||
* Create a typed error from a standard Error
|
||||
* Useful for converting errors from external libraries or APIs
|
||||
*
|
||||
* @param error Standard error to convert
|
||||
* @param code Error code to assign
|
||||
* @param contextData Additional context data
|
||||
* @returns Typed PlatformError
|
||||
*/
|
||||
export function fromError(
|
||||
error: Error,
|
||||
code: string,
|
||||
contextData: Record<string, any> = {}
|
||||
) {
|
||||
// Import and use PlatformError
|
||||
const { PlatformError } = require('./base.errors.js');
|
||||
const { ErrorSeverity, ErrorCategory, ErrorRecoverability } = require('./error.codes.js');
|
||||
|
||||
return new PlatformError(
|
||||
error.message,
|
||||
code,
|
||||
ErrorSeverity.MEDIUM,
|
||||
ErrorCategory.OPERATION,
|
||||
ErrorRecoverability.NON_RECOVERABLE,
|
||||
{
|
||||
data: {
|
||||
...contextData,
|
||||
originalError: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an error is retryable
|
||||
*
|
||||
* @param error Error to check
|
||||
* @returns Boolean indicating if the error should be retried
|
||||
*/
|
||||
export function isRetryable(error: any): boolean {
|
||||
// If it's our platform error, use its recoverability property
|
||||
if (error && typeof error === 'object' && 'recoverability' in error) {
|
||||
const { ErrorRecoverability } = require('./error.codes.js');
|
||||
return error.recoverability === ErrorRecoverability.RECOVERABLE ||
|
||||
error.recoverability === ErrorRecoverability.MAYBE_RECOVERABLE ||
|
||||
error.recoverability === ErrorRecoverability.TRANSIENT;
|
||||
}
|
||||
|
||||
// Check if it's a network error (these are often transient)
|
||||
if (error && typeof error === 'object' && error.code) {
|
||||
const networkErrors = [
|
||||
'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EHOSTUNREACH',
|
||||
'ENETUNREACH', 'ENOTFOUND', 'EPROTO', 'ECONNABORTED'
|
||||
];
|
||||
|
||||
return networkErrors.includes(error.code);
|
||||
}
|
||||
|
||||
// By default, we can't determine if the error is retryable
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapped version of a function that catches errors
|
||||
* and converts them to typed PlatformErrors
|
||||
*
|
||||
* @param fn Function to wrap
|
||||
* @param errorCode Default error code to use
|
||||
* @param contextData Additional context data
|
||||
* @returns Wrapped function
|
||||
*/
|
||||
export function withErrorHandling<T extends (...args: any[]) => Promise<any>>(
|
||||
fn: T,
|
||||
errorCode: string,
|
||||
contextData: Record<string, any> = {}
|
||||
): T {
|
||||
return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {
|
||||
try {
|
||||
return await fn(...args);
|
||||
} catch (error) {
|
||||
if (error && typeof error === 'object' && 'code' in error) {
|
||||
// Already a typed error, rethrow
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw fromError(
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
errorCode,
|
||||
{
|
||||
...contextData,
|
||||
fnName: fn.name,
|
||||
args: args.map(arg =>
|
||||
typeof arg === 'object'
|
||||
? '[Object]'
|
||||
: String(arg).substring(0, 100)
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
}) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry a function with exponential backoff
|
||||
*
|
||||
* @param fn Function to retry
|
||||
* @param options Retry options
|
||||
* @returns Function result or throws after max retries
|
||||
*/
|
||||
export async function retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: {
|
||||
maxRetries?: number;
|
||||
initialDelay?: number;
|
||||
maxDelay?: number;
|
||||
backoffFactor?: number;
|
||||
retryableErrors?: Array<string | RegExp>;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
const {
|
||||
maxRetries = 3,
|
||||
initialDelay = 1000,
|
||||
maxDelay = 30000,
|
||||
backoffFactor = 2,
|
||||
retryableErrors = []
|
||||
} = options;
|
||||
|
||||
let lastError: Error;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error
|
||||
? error
|
||||
: new Error(String(error));
|
||||
|
||||
// Check if we should retry
|
||||
const shouldRetry = attempt < maxRetries && (
|
||||
isRetryable(error) ||
|
||||
retryableErrors.some(pattern => {
|
||||
if (typeof pattern === 'string') {
|
||||
return lastError.message.includes(pattern);
|
||||
}
|
||||
return pattern.test(lastError.message);
|
||||
})
|
||||
);
|
||||
|
||||
if (!shouldRetry) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
const delay = Math.min(initialDelay * Math.pow(backoffFactor, attempt), maxDelay);
|
||||
|
||||
// Add jitter to prevent thundering herd problem (±20%)
|
||||
const jitter = 0.8 + Math.random() * 0.4;
|
||||
const actualDelay = Math.floor(delay * jitter);
|
||||
|
||||
// Wait before next retry
|
||||
await new Promise(resolve => setTimeout(resolve, actualDelay));
|
||||
}
|
||||
}
|
||||
|
||||
// This should never happen, but TypeScript needs it
|
||||
throw lastError!;
|
||||
}
|
611
ts/errors/mta.errors.ts
Normal file
611
ts/errors/mta.errors.ts
Normal file
@ -0,0 +1,611 @@
|
||||
import {
|
||||
PlatformError,
|
||||
NetworkError,
|
||||
AuthenticationError,
|
||||
OperationError,
|
||||
ConfigurationError
|
||||
} from './base.errors.js';
|
||||
import type { IErrorContext } from './base.errors.js';
|
||||
|
||||
import {
|
||||
MTA_CONNECTION_ERROR,
|
||||
MTA_AUTHENTICATION_ERROR,
|
||||
MTA_DELIVERY_ERROR,
|
||||
MTA_CONFIGURATION_ERROR,
|
||||
MTA_DNS_ERROR,
|
||||
MTA_TIMEOUT_ERROR,
|
||||
MTA_PROTOCOL_ERROR
|
||||
} from './error.codes.js';
|
||||
|
||||
/**
|
||||
* Base class for MTA connection errors
|
||||
*/
|
||||
export class MtaConnectionError extends NetworkError {
|
||||
/**
|
||||
* Creates a new MTA connection error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_CONNECTION_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a DNS resolution error
|
||||
*
|
||||
* @param hostname Hostname that failed to resolve
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static dnsError(
|
||||
hostname: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): MtaConnectionError {
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
return new MtaConnectionError(
|
||||
`Failed to resolve DNS for ${hostname}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
hostname,
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
},
|
||||
userMessage: `Could not connect to mail server for ${hostname}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a connection timeout
|
||||
*
|
||||
* @param hostname Hostname that timed out
|
||||
* @param port Port number
|
||||
* @param timeout Timeout in milliseconds
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static timeout(
|
||||
hostname: string,
|
||||
port: number,
|
||||
timeout: number,
|
||||
context: IErrorContext = {}
|
||||
): MtaConnectionError {
|
||||
return new MtaConnectionError(
|
||||
`Connection to ${hostname}:${port} timed out after ${timeout}ms`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
hostname,
|
||||
port,
|
||||
timeout
|
||||
},
|
||||
userMessage: `Connection to mail server timed out.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a connection refused error
|
||||
*
|
||||
* @param hostname Hostname that refused connection
|
||||
* @param port Port number
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static refused(
|
||||
hostname: string,
|
||||
port: number,
|
||||
context: IErrorContext = {}
|
||||
): MtaConnectionError {
|
||||
return new MtaConnectionError(
|
||||
`Connection to ${hostname}:${port} refused`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
hostname,
|
||||
port
|
||||
},
|
||||
userMessage: `Connection to mail server was refused.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA authentication errors
|
||||
*/
|
||||
export class MtaAuthenticationError extends AuthenticationError {
|
||||
/**
|
||||
* Creates a new MTA authentication error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_AUTHENTICATION_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for invalid credentials
|
||||
*
|
||||
* @param hostname Hostname where authentication failed
|
||||
* @param username Username that failed authentication
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static invalidCredentials(
|
||||
hostname: string,
|
||||
username: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaAuthenticationError {
|
||||
return new MtaAuthenticationError(
|
||||
`Authentication failed for user ${username} at ${hostname}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
hostname,
|
||||
username
|
||||
},
|
||||
userMessage: `Authentication to mail server failed.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for unsupported authentication method
|
||||
*
|
||||
* @param hostname Hostname
|
||||
* @param method Authentication method that is not supported
|
||||
* @param supportedMethods List of supported authentication methods
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static unsupportedMethod(
|
||||
hostname: string,
|
||||
method: string,
|
||||
supportedMethods: string[] = [],
|
||||
context: IErrorContext = {}
|
||||
): MtaAuthenticationError {
|
||||
return new MtaAuthenticationError(
|
||||
`Authentication method ${method} not supported by ${hostname}${supportedMethods.length > 0 ? `. Supported methods: ${supportedMethods.join(', ')}` : ''}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
hostname,
|
||||
method,
|
||||
supportedMethods
|
||||
},
|
||||
userMessage: `The mail server doesn't support the required authentication method.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA delivery errors
|
||||
*/
|
||||
export class MtaDeliveryError extends OperationError {
|
||||
/**
|
||||
* Creates a new MTA delivery error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_DELIVERY_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a permanent delivery failure
|
||||
*
|
||||
* @param message Error message
|
||||
* @param recipientAddress Recipient email address
|
||||
* @param statusCode SMTP status code
|
||||
* @param smtpResponse Full SMTP response
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static permanent(
|
||||
message: string,
|
||||
recipientAddress: string,
|
||||
statusCode?: string,
|
||||
smtpResponse?: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaDeliveryError {
|
||||
const statusCodeStr = statusCode ? ` (${statusCode})` : '';
|
||||
return new MtaDeliveryError(
|
||||
`Permanent delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
recipientAddress,
|
||||
statusCode,
|
||||
smtpResponse,
|
||||
permanent: true
|
||||
},
|
||||
userMessage: `The email could not be delivered to ${recipientAddress}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a temporary delivery failure
|
||||
*
|
||||
* @param message Error message
|
||||
* @param recipientAddress Recipient email address
|
||||
* @param statusCode SMTP status code
|
||||
* @param smtpResponse Full SMTP response
|
||||
* @param maxRetries Maximum number of retries
|
||||
* @param currentRetry Current retry count
|
||||
* @param retryDelay Delay between retries in ms
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static temporary(
|
||||
message: string,
|
||||
recipientAddress: string,
|
||||
statusCode?: string,
|
||||
smtpResponse?: string,
|
||||
maxRetries: number = 3,
|
||||
currentRetry: number = 0,
|
||||
retryDelay: number = 60000,
|
||||
context: IErrorContext = {}
|
||||
): MtaDeliveryError {
|
||||
const statusCodeStr = statusCode ? ` (${statusCode})` : '';
|
||||
const error = new MtaDeliveryError(
|
||||
`Temporary delivery failure to ${recipientAddress}${statusCodeStr}: ${message}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
recipientAddress,
|
||||
statusCode,
|
||||
smtpResponse,
|
||||
permanent: false
|
||||
},
|
||||
userMessage: `The email delivery to ${recipientAddress} failed temporarily. It will be retried.`
|
||||
}
|
||||
);
|
||||
|
||||
return error.withRetry(maxRetries, currentRetry, retryDelay) as MtaDeliveryError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a permanent delivery failure
|
||||
*/
|
||||
public isPermanent(): boolean {
|
||||
return !!this.context.data?.permanent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recipient address associated with this delivery error
|
||||
*/
|
||||
public getRecipientAddress(): string | undefined {
|
||||
return this.context.data?.recipientAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SMTP status code associated with this delivery error
|
||||
*/
|
||||
public getStatusCode(): string | undefined {
|
||||
return this.context.data?.statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA configuration errors
|
||||
*/
|
||||
export class MtaConfigurationError extends ConfigurationError {
|
||||
/**
|
||||
* Creates a new MTA configuration error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_CONFIGURATION_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a missing configuration value
|
||||
*
|
||||
* @param propertyPath Path to the missing property
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static missingConfig(
|
||||
propertyPath: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaConfigurationError {
|
||||
return new MtaConfigurationError(
|
||||
`Missing required configuration: ${propertyPath}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
propertyPath
|
||||
},
|
||||
userMessage: `The mail server is missing required configuration.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an invalid configuration value
|
||||
*
|
||||
* @param propertyPath Path to the invalid property
|
||||
* @param value Current value
|
||||
* @param expectedType Expected type or format
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static invalidConfig(
|
||||
propertyPath: string,
|
||||
value: any,
|
||||
expectedType: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaConfigurationError {
|
||||
return new MtaConfigurationError(
|
||||
`Invalid configuration value for ${propertyPath}: got ${value} (${typeof value}), expected ${expectedType}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
propertyPath,
|
||||
value,
|
||||
expectedType
|
||||
},
|
||||
userMessage: `The mail server has an invalid configuration value.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA DNS errors
|
||||
*/
|
||||
export class MtaDnsError extends NetworkError {
|
||||
/**
|
||||
* Creates a new MTA DNS error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_DNS_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an MX record lookup failure
|
||||
*
|
||||
* @param domain Domain that failed MX lookup
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static mxLookupFailed(
|
||||
domain: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): MtaDnsError {
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
return new MtaDnsError(
|
||||
`Failed to lookup MX records for ${domain}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
domain,
|
||||
recordType: 'MX',
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
},
|
||||
userMessage: `Could not find mail servers for ${domain}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a TXT record lookup failure
|
||||
*
|
||||
* @param domain Domain that failed TXT lookup
|
||||
* @param recordPrefix Optional record prefix (e.g., 'spf', 'dkim', 'dmarc')
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static txtLookupFailed(
|
||||
domain: string,
|
||||
recordPrefix?: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): MtaDnsError {
|
||||
const recordType = recordPrefix ? `${recordPrefix} TXT` : 'TXT';
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
|
||||
return new MtaDnsError(
|
||||
`Failed to lookup ${recordType} records for ${domain}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
domain,
|
||||
recordType,
|
||||
recordPrefix,
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
},
|
||||
userMessage: `Could not verify ${recordPrefix || ''} records for ${domain}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA timeout errors
|
||||
*/
|
||||
export class MtaTimeoutError extends NetworkError {
|
||||
/**
|
||||
* Creates a new MTA timeout error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_TIMEOUT_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an SMTP command timeout
|
||||
*
|
||||
* @param command SMTP command that timed out
|
||||
* @param server Server hostname
|
||||
* @param timeout Timeout in milliseconds
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static commandTimeout(
|
||||
command: string,
|
||||
server: string,
|
||||
timeout: number,
|
||||
context: IErrorContext = {}
|
||||
): MtaTimeoutError {
|
||||
return new MtaTimeoutError(
|
||||
`SMTP command ${command} to ${server} timed out after ${timeout}ms`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
command,
|
||||
server,
|
||||
timeout
|
||||
},
|
||||
userMessage: `The mail server took too long to respond.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an overall transaction timeout
|
||||
*
|
||||
* @param server Server hostname
|
||||
* @param timeout Timeout in milliseconds
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static transactionTimeout(
|
||||
server: string,
|
||||
timeout: number,
|
||||
context: IErrorContext = {}
|
||||
): MtaTimeoutError {
|
||||
return new MtaTimeoutError(
|
||||
`SMTP transaction with ${server} timed out after ${timeout}ms`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
server,
|
||||
timeout
|
||||
},
|
||||
userMessage: `The mail server transaction took too long to complete.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for MTA protocol errors
|
||||
*/
|
||||
export class MtaProtocolError extends OperationError {
|
||||
/**
|
||||
* Creates a new MTA protocol error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, MTA_PROTOCOL_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an unexpected server response
|
||||
*
|
||||
* @param command SMTP command that received unexpected response
|
||||
* @param response Unexpected response
|
||||
* @param expected Expected response pattern
|
||||
* @param server Server hostname
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static unexpectedResponse(
|
||||
command: string,
|
||||
response: string,
|
||||
expected: string,
|
||||
server: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaProtocolError {
|
||||
return new MtaProtocolError(
|
||||
`Unexpected SMTP response from ${server} for command ${command}: got "${response}", expected "${expected}"`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
command,
|
||||
response,
|
||||
expected,
|
||||
server
|
||||
},
|
||||
userMessage: `Received an unexpected response from the mail server.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a syntax error
|
||||
*
|
||||
* @param details Error details
|
||||
* @param server Server hostname
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static syntaxError(
|
||||
details: string,
|
||||
server: string,
|
||||
context: IErrorContext = {}
|
||||
): MtaProtocolError {
|
||||
return new MtaProtocolError(
|
||||
`SMTP syntax error in communication with ${server}: ${details}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
details,
|
||||
server
|
||||
},
|
||||
userMessage: `There was a protocol error communicating with the mail server.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
352
ts/errors/reputation.errors.ts
Normal file
352
ts/errors/reputation.errors.ts
Normal file
@ -0,0 +1,352 @@
|
||||
import {
|
||||
PlatformError,
|
||||
OperationError,
|
||||
ResourceError
|
||||
} from './base.errors.js';
|
||||
import type { IErrorContext } from './base.errors.js';
|
||||
|
||||
import {
|
||||
REPUTATION_CHECK_ERROR,
|
||||
REPUTATION_DATA_ERROR,
|
||||
REPUTATION_BLOCKLIST_ERROR,
|
||||
REPUTATION_UPDATE_ERROR,
|
||||
WARMUP_ALLOCATION_ERROR,
|
||||
WARMUP_LIMIT_EXCEEDED,
|
||||
WARMUP_SCHEDULE_ERROR
|
||||
} from './error.codes.js';
|
||||
|
||||
/**
|
||||
* Base class for reputation-related errors
|
||||
*/
|
||||
export class ReputationError extends OperationError {
|
||||
/**
|
||||
* Creates a new reputation error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param code Error code
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
code: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, code, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for reputation check errors
|
||||
*/
|
||||
export class ReputationCheckError extends ReputationError {
|
||||
/**
|
||||
* Creates a new reputation check error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, REPUTATION_CHECK_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an IP reputation check error
|
||||
*
|
||||
* @param ip IP address
|
||||
* @param provider Reputation provider
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static ipCheckFailed(
|
||||
ip: string,
|
||||
provider: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): ReputationCheckError {
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
return new ReputationCheckError(
|
||||
`Failed to check reputation for IP ${ip} with provider ${provider}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
ip,
|
||||
provider,
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a domain reputation check error
|
||||
*
|
||||
* @param domain Domain
|
||||
* @param provider Reputation provider
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static domainCheckFailed(
|
||||
domain: string,
|
||||
provider: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): ReputationCheckError {
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
return new ReputationCheckError(
|
||||
`Failed to check reputation for domain ${domain} with provider ${provider}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
domain,
|
||||
provider,
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for reputation data errors
|
||||
*/
|
||||
export class ReputationDataError extends ReputationError {
|
||||
/**
|
||||
* Creates a new reputation data error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, REPUTATION_DATA_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for a data access error
|
||||
*
|
||||
* @param entity Entity type (domain, ip)
|
||||
* @param entityId Entity identifier
|
||||
* @param operation Operation that failed (read, write, update)
|
||||
* @param originalError Original error
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static dataAccessFailed(
|
||||
entity: string,
|
||||
entityId: string,
|
||||
operation: string,
|
||||
originalError?: Error,
|
||||
context: IErrorContext = {}
|
||||
): ReputationDataError {
|
||||
const errorMsg = originalError ? `: ${originalError.message}` : '';
|
||||
return new ReputationDataError(
|
||||
`Failed to ${operation} reputation data for ${entity} ${entityId}${errorMsg}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
entity,
|
||||
entityId,
|
||||
operation,
|
||||
originalError: originalError ? {
|
||||
message: originalError.message,
|
||||
stack: originalError.stack
|
||||
} : undefined
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for blocklist-related errors
|
||||
*/
|
||||
export class BlocklistError extends ReputationError {
|
||||
/**
|
||||
* Creates a new blocklist error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, REPUTATION_BLOCKLIST_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for an entity found on a blocklist
|
||||
*
|
||||
* @param entity Entity type (domain, ip)
|
||||
* @param entityId Entity identifier
|
||||
* @param blocklist Blocklist name
|
||||
* @param reason Reason for listing (if available)
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static entityBlocked(
|
||||
entity: string,
|
||||
entityId: string,
|
||||
blocklist: string,
|
||||
reason?: string,
|
||||
context: IErrorContext = {}
|
||||
): BlocklistError {
|
||||
const reasonText = reason ? ` (${reason})` : '';
|
||||
return new BlocklistError(
|
||||
`${entity.charAt(0).toUpperCase() + entity.slice(1)} ${entityId} is listed on blocklist ${blocklist}${reasonText}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
entity,
|
||||
entityId,
|
||||
blocklist,
|
||||
reason
|
||||
},
|
||||
userMessage: `The ${entity} ${entityId} is on a blocklist. This may affect email deliverability.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for reputation update errors
|
||||
*/
|
||||
export class ReputationUpdateError extends ReputationError {
|
||||
/**
|
||||
* Creates a new reputation update error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, REPUTATION_UPDATE_ERROR, context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for IP warmup allocation errors
|
||||
*/
|
||||
export class WarmupAllocationError extends ReputationError {
|
||||
/**
|
||||
* Creates a new warmup allocation error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, WARMUP_ALLOCATION_ERROR, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for no available IPs
|
||||
*
|
||||
* @param domain Domain requesting an IP
|
||||
* @param policy Allocation policy that was used
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static noAvailableIps(
|
||||
domain: string,
|
||||
policy: string,
|
||||
context: IErrorContext = {}
|
||||
): WarmupAllocationError {
|
||||
return new WarmupAllocationError(
|
||||
`No available IPs for domain ${domain} using ${policy} allocation policy`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
domain,
|
||||
policy
|
||||
},
|
||||
userMessage: `No available sending IPs for ${domain}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for IP warmup limit exceeded errors
|
||||
*/
|
||||
export class WarmupLimitError extends ResourceError {
|
||||
/**
|
||||
* Creates a new warmup limit error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, WARMUP_LIMIT_EXCEEDED, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance for daily sending limit exceeded
|
||||
*
|
||||
* @param ip IP address
|
||||
* @param domain Domain
|
||||
* @param limit Daily limit
|
||||
* @param sent Number of emails sent
|
||||
* @param context Additional context
|
||||
*/
|
||||
public static dailyLimitExceeded(
|
||||
ip: string,
|
||||
domain: string,
|
||||
limit: number,
|
||||
sent: number,
|
||||
context: IErrorContext = {}
|
||||
): WarmupLimitError {
|
||||
return new WarmupLimitError(
|
||||
`Daily sending limit exceeded for IP ${ip} and domain ${domain}: ${sent}/${limit}`,
|
||||
{
|
||||
...context,
|
||||
data: {
|
||||
...context.data,
|
||||
ip,
|
||||
domain,
|
||||
limit,
|
||||
sent
|
||||
},
|
||||
userMessage: `Daily sending limit reached for ${domain}.`
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for IP warmup schedule errors
|
||||
*/
|
||||
export class WarmupScheduleError extends ReputationError {
|
||||
/**
|
||||
* Creates a new warmup schedule error
|
||||
*
|
||||
* @param message Error message
|
||||
* @param context Additional context
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
context: IErrorContext = {}
|
||||
) {
|
||||
super(message, WARMUP_SCHEDULE_ERROR, context);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user