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.` } ); } }