/** * Machine-readable error codes for programmatic error handling * These codes allow API consumers to handle specific error types reliably */ export enum ErrorCode { // Authentication & Authorization (401-403) UNAUTHORIZED = 'UNAUTHORIZED', INVALID_CREDENTIALS = 'INVALID_CREDENTIALS', MISSING_AUTH = 'MISSING_AUTH', INVALID_API_KEY = 'INVALID_API_KEY', FORBIDDEN = 'FORBIDDEN', PROJECT_ACCESS_DENIED = 'PROJECT_ACCESS_DENIED', PROJECT_DISABLED = 'PROJECT_DISABLED', EMAIL_VERIFICATION_REQUIRED = 'EMAIL_VERIFICATION_REQUIRED', // Resource Errors (404-409) RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND', CONTACT_NOT_FOUND = 'CONTACT_NOT_FOUND', TEMPLATE_NOT_FOUND = 'TEMPLATE_NOT_FOUND', CAMPAIGN_NOT_FOUND = 'CAMPAIGN_NOT_FOUND', WORKFLOW_NOT_FOUND = 'WORKFLOW_NOT_FOUND', CONFLICT = 'CONFLICT', // Validation & Input Errors (400, 422) BAD_REQUEST = 'BAD_REQUEST', VALIDATION_ERROR = 'VALIDATION_ERROR', INVALID_EMAIL = 'INVALID_EMAIL', INVALID_REQUEST_BODY = 'INVALID_REQUEST_BODY', MISSING_REQUIRED_FIELD = 'MISSING_REQUIRED_FIELD', // Rate Limiting & Billing (429, 402) RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', BILLING_LIMIT_EXCEEDED = 'BILLING_LIMIT_EXCEEDED', UPGRADE_REQUIRED = 'UPGRADE_REQUIRED', // Server Errors (500+) INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', DATABASE_ERROR = 'DATABASE_ERROR', EXTERNAL_SERVICE_ERROR = 'EXTERNAL_SERVICE_ERROR', } /** * Structured field-level validation error */ export interface FieldError { field: string; // Dot-notation path (e.g., "email", "data.firstName") message: string; // Human-readable error message code: string; // Validation error code (e.g., "invalid_email", "too_short") received?: unknown; // The invalid value that was provided } /** * Base HTTP exception with support for error codes and structured data */ export class HttpException extends Error { public constructor( public readonly code: number, message: string, public readonly errorCode?: ErrorCode, public readonly details?: Record, ) { super(message); this.name = this.constructor.name; } } /** * Resource not found (404) */ export class NotFound extends HttpException { /** * Construct a new NotFound exception * @param resource The type of resource that was not found * @param id Optional resource identifier to include in the message */ public constructor(resource: string, id?: string) { const message = id ? `${resource} with ID "${id}" was not found` : `That ${resource.toLowerCase()} was not found`; // Map common resources to specific error codes const errorCodeMap: Record = { contact: ErrorCode.CONTACT_NOT_FOUND, template: ErrorCode.TEMPLATE_NOT_FOUND, campaign: ErrorCode.CAMPAIGN_NOT_FOUND, workflow: ErrorCode.WORKFLOW_NOT_FOUND, }; const errorCode = errorCodeMap[resource.toLowerCase()] || ErrorCode.RESOURCE_NOT_FOUND; super(404, message, errorCode, {resource, id}); } } /** * Forbidden - user is not allowed to perform this action (403) */ export class NotAllowed extends HttpException { /** * Construct a new NotAllowed exception * @param msg Custom error message * @param reason Optional reason for the forbidden action */ public constructor(msg = 'You are not allowed to perform this action', reason?: string) { super(403, msg, ErrorCode.FORBIDDEN, reason ? {reason} : undefined); } } /** * Unauthorized - user needs to authenticate (401) */ export class NotAuthenticated extends HttpException { public constructor(message = 'You need to be authenticated to do this', errorCode = ErrorCode.UNAUTHORIZED) { super(401, message, errorCode); } } /** * Payment required - user needs to upgrade plan (402) */ export class NeedToUpgrade extends HttpException { public constructor(msg = 'You need to upgrade your plan to do this') { super(402, msg, ErrorCode.UPGRADE_REQUIRED); } } /** * Validation error with field-level details (422) */ export class ValidationError extends HttpException { /** * Construct a new ValidationError exception * @param errors Array of field-level validation errors * @param message Optional override for the main error message */ public constructor( public readonly errors: FieldError[], message = 'Validation failed', ) { super(422, message, ErrorCode.VALIDATION_ERROR, {errors}); } } /** * Conflict - resource already exists or state conflict (409) */ export class ConflictError extends HttpException { public constructor(message: string, details?: Record) { super(409, message, ErrorCode.CONFLICT, details); } } /** * Rate limit exceeded (429) */ export class RateLimitError extends HttpException { public constructor(message = 'Rate limit exceeded. Please try again later.', retryAfter?: number) { super(429, message, ErrorCode.RATE_LIMIT_EXCEEDED, retryAfter ? {retryAfter} : undefined); } } /** * Bad request - generic client error (400) */ export class BadRequest extends HttpException { public constructor(message: string, errorCode = ErrorCode.BAD_REQUEST, details?: Record) { super(400, message, errorCode, details); } }