exceptions.py
1 """ 2 Generic web exceptions for HTTP/REST APIs. 3 4 This module provides a comprehensive set of generic exception classes 5 that can be used by any web application for consistent error handling. 6 """ 7 8 from typing import Any, Dict, List, Optional 9 from enum import Enum 10 11 12 class EntityOperation(Enum): 13 """Operations that can be performed on entities.""" 14 CREATE = "create" 15 UPDATE = "update" 16 DELETE = "delete" 17 READ = "read" 18 19 20 class WebUIBackendException(Exception): 21 """Base exception for all web UI backend errors.""" 22 23 def __init__(self, message: str, details: Optional[Dict[str, Any]] = None): 24 self.message = message 25 self.details = details or {} 26 super().__init__(self.message) 27 28 29 class ValidationError(WebUIBackendException): 30 """ 31 Exception for validation errors with field-level details. 32 33 This exception supports both simple validation messages and detailed 34 field-level validation errors with user-friendly message formatting. 35 """ 36 37 def __init__( 38 self, 39 message: str, 40 validation_details: Optional[Dict[str, List[str]]] = None, 41 entity_type: Optional[str] = None, 42 entity_identifier: Optional[str] = None 43 ): 44 self.validation_details = validation_details or {} 45 self.entity_type = entity_type 46 self.entity_identifier = entity_identifier 47 super().__init__(message) 48 49 def get_user_friendly_message(self, operation: EntityOperation) -> str: 50 """Generate a user-friendly error message based on context.""" 51 if not self.entity_identifier or not self.entity_type: 52 return self.message 53 54 if self.validation_details: 55 field = next(iter(self.validation_details.keys())) 56 error_msg = self.validation_details[field][0] 57 return f"Unable to {operation.value} {self.entity_type} {self.entity_identifier} because of a problem with the {field}: {error_msg.lower()}" 58 59 if self.message: 60 return f"Unable to {operation.value} {self.entity_type} {self.entity_identifier}: {self.message.lower()}" 61 62 return self.message 63 64 @classmethod 65 def builder(cls): 66 """Create a ValidationErrorBuilder for fluent error construction.""" 67 return ValidationErrorBuilder() 68 69 70 class ValidationErrorBuilder: 71 """Builder for constructing ValidationError instances with fluent API.""" 72 73 def __init__(self): 74 self._message = "" 75 self._validation_details = {} 76 self._entity_type = None 77 self._entity_identifier = None 78 79 def message(self, message: str): 80 """Set the main error message.""" 81 self._message = message 82 return self 83 84 def formatted_message(self, pattern: str, *args): 85 """Set a formatted error message.""" 86 self._message = pattern.format(*args) 87 return self 88 89 def validation_detail(self, field: str, errors: List[str]): 90 """Add validation details for a specific field.""" 91 self._validation_details[field] = errors 92 return self 93 94 def formatted_validation_detail(self, field: str, pattern: str, *args): 95 """Add a formatted validation detail for a field.""" 96 error_msg = pattern.format(*args) 97 self._validation_details[field] = [error_msg] 98 return self 99 100 def entity_type(self, entity_type: str): 101 """Set the entity type for context.""" 102 self._entity_type = entity_type 103 return self 104 105 def entity_identifier(self, identifier: str): 106 """Set the entity identifier for context.""" 107 self._entity_identifier = identifier 108 return self 109 110 def build(self) -> ValidationError: 111 """Build the ValidationError instance.""" 112 return ValidationError( 113 self._message, 114 self._validation_details, 115 self._entity_type, 116 self._entity_identifier 117 ) 118 119 120 class EntityNotFoundError(WebUIBackendException): 121 """ 122 Generic exception for when an entity is not found. 123 124 This replaces all specific "NotFound" exceptions with a single generic one. 125 Format: "Could not find {entity_type} with id: {entity_id}" 126 """ 127 128 def __init__(self, entity_type: str, entity_id: str): 129 self.entity_type = entity_type 130 self.entity_id = str(entity_id) 131 message = f"Could not find {entity_type} with id: {self.entity_id}" 132 details = {"entity_type": entity_type, "entity_id": self.entity_id} 133 super().__init__(message, details) 134 135 136 class EntityAlreadyExistsError(WebUIBackendException): 137 """Exception for when an entity already exists.""" 138 139 def __init__(self, entity_type: str, identifier: str, value: Any = None): 140 self.entity_type = entity_type 141 self.identifier = identifier 142 self.value = str(value) if value is not None else None 143 144 if value is not None: 145 message = f"{entity_type} with {identifier} '{self.value}' already exists" 146 else: 147 message = f"{entity_type} already exists" 148 149 details = {"entity_type": entity_type, "identifier": identifier} 150 if self.value: 151 details["value"] = self.value 152 153 super().__init__(message, details) 154 155 156 class BusinessRuleViolationError(WebUIBackendException): 157 """Exception for business rule violations.""" 158 159 def __init__(self, rule: str, message: str): 160 self.rule = rule 161 details = {"rule": rule} 162 super().__init__(message, details) 163 164 165 class ConfigurationError(WebUIBackendException): 166 """Exception for configuration-related errors.""" 167 168 def __init__(self, component: str, message: str): 169 self.component = component 170 details = {"component": component} 171 super().__init__(message, details) 172 173 174 class DataIntegrityError(WebUIBackendException): 175 """Exception for data integrity violations.""" 176 177 def __init__(self, constraint: str, message: str): 178 self.constraint = constraint 179 details = {"constraint": constraint} 180 super().__init__(message, details) 181 182 183 class ExternalServiceError(WebUIBackendException): 184 """Exception for external service communication errors.""" 185 186 def __init__(self, service: str, message: str, status_code: Optional[int] = None): 187 self.service = service 188 self.status_code = status_code 189 details = {"service": service} 190 if status_code: 191 details["status_code"] = status_code 192 super().__init__(message, details) 193 194 195 class InternalServiceError(WebUIBackendException): 196 """Exception for unexpected internal errors that should never happen.""" 197 198 def __init__(self, message: str = "An unexpected error occurred"): 199 super().__init__(message)