An ATProtocol powered blogging engine.
at main 10 kB view raw
1use axum::response::{IntoResponse, Response}; 2use reqwest::StatusCode; 3use thiserror::Error; 4 5/// Main error type for the Blahg application 6/// 7/// All errors follow the format: error-blahg-<domain>-<number> <message>: <details> 8/// Domains are organized alphabetically: config, consumer, http, process, storage 9#[derive(Error, Debug)] 10pub enum BlahgError { 11 // Config domain - Configuration-related errors 12 #[error("error-blahg-config-1 Failed to parse HTTP client timeout: {details}")] 13 /// Failed to parse HTTP client timeout from environment variable. 14 ConfigHttpTimeoutInvalid { 15 /// Details about the parsing error. 16 details: String, 17 }, 18 19 #[error("error-blahg-config-2 Failed to parse HTTP port: {port}")] 20 /// Failed to parse HTTP port from environment variable. 21 ConfigHttpPortInvalid { 22 /// The invalid port value. 23 port: String, 24 }, 25 26 #[error("error-blahg-config-3 Invalid S3 URL format: {details}")] 27 /// Failed to parse S3 URL format from environment variable. 28 ConfigS3UrlInvalid { 29 /// Details about the S3 URL parsing error. 30 details: String, 31 }, 32 33 #[error("error-blahg-config-4 Required feature not enabled: {feature}")] 34 /// Required feature is not enabled in the build. 35 ConfigFeatureNotEnabled { 36 /// The feature that is required but not enabled. 37 feature: String, 38 }, 39 40 // Consumer domain - Jetstream consumer errors 41 /// Error when a DNS nameserver IP cannot be parsed. 42 /// 43 /// This error occurs when the DNS_NAMESERVERS environment variable contains 44 /// an IP address that cannot be parsed as a valid IpAddr. 45 #[error("error-blahg-config-5 Unable to parse nameserver IP '{0}': {1}")] 46 NameserverParsingFailed(String, std::net::AddrParseError), 47 48 // Consumer domain - Jetstream consumer errors 49 #[error("error-blahg-consumer-1 Failed to send badge event to queue: {details}")] 50 /// Failed to send badge event to the processing queue. 51 ConsumerQueueSendFailed { 52 /// Details about the send failure. 53 details: String, 54 }, 55 56 #[error("error-blahg-consumer-2 Jetstream disconnected: {details}")] 57 /// Jetstream consumer disconnected. 58 ConsumerDisconnected { 59 /// Details about the disconnection. 60 details: String, 61 }, 62 63 #[error( 64 "error-blahg-consumer-3 Jetstream disconnect rate exceeded: {disconnect_count} disconnects in {duration_mins} minutes" 65 )] 66 /// Jetstream consumer disconnect rate exceeded the allowable limit. 67 ConsumerDisconnectRateExceeded { 68 /// Number of disconnects in the time period. 69 disconnect_count: usize, 70 /// Duration in minutes for the disconnect rate calculation. 71 duration_mins: u64, 72 }, 73 74 // HTTP domain - HTTP server and template errors 75 #[error("error-blahg-http-1 Template rendering failed: {template}")] 76 /// Template rendering failed in HTTP response. 77 HttpTemplateRenderFailed { 78 /// The template that failed to render. 79 template: String, 80 }, 81 82 #[error("error-blahg-http-2 Internal server error")] 83 /// Generic internal server error for HTTP responses. 84 HttpInternalServerError, 85 86 // Process domain - Badge processing errors 87 #[error("error-blahg-process-1 Invalid AT-URI format: {uri}")] 88 /// Invalid AT-URI format encountered during processing. 89 ProcessInvalidAturi { 90 /// The invalid URI string. 91 uri: String, 92 }, 93 94 #[error("error-blahg-process-2 Failed to resolve identity for {did}: {details}")] 95 /// Identity resolution failed with detailed error information. 96 ProcessIdentityResolutionFailed { 97 /// The DID that could not be resolved. 98 did: String, 99 /// Detailed error information. 100 details: String, 101 }, 102 103 #[error("error-blahg-process-3 Failed to fetch badge record: {uri}")] 104 /// Failed to fetch badge record from AT Protocol. 105 ProcessBadgeFetchFailed { 106 /// The badge URI that could not be fetched. 107 uri: String, 108 }, 109 110 #[error("error-blahg-process-4 Failed to fetch badge {uri}: {details}")] 111 /// Badge record fetch failed with detailed error information. 112 ProcessBadgeRecordFetchFailed { 113 /// The badge URI that could not be fetched. 114 uri: String, 115 /// Detailed error information. 116 details: String, 117 }, 118 119 #[error("error-blahg-process-5 Failed to download badge image: {image_ref}")] 120 /// Failed to download badge image. 121 ProcessImageDownloadFailed { 122 /// Reference to the image that failed to download. 123 image_ref: String, 124 }, 125 126 #[error("error-blahg-process-6 Failed to process badge event: {event_type}")] 127 /// Failed to process a badge event from Jetstream. 128 ProcessEventHandlingFailed { 129 /// Type of event that failed to process. 130 event_type: String, 131 }, 132 133 #[error("error-blahg-process-7 Image file too large: {size} bytes exceeds 3MB limit")] 134 /// Badge image file exceeds maximum allowed size. 135 ProcessImageTooLarge { 136 /// The actual size of the image in bytes. 137 size: usize, 138 }, 139 140 #[error("error-blahg-process-8 Failed to decode image: {details}")] 141 /// Failed to decode badge image data. 142 ProcessImageDecodeFailed { 143 /// Details about the decode error. 144 details: String, 145 }, 146 147 #[error("error-blahg-process-9 Unsupported image format: {format}")] 148 /// Badge image is in an unsupported format. 149 ProcessUnsupportedImageFormat { 150 /// The unsupported image format. 151 format: String, 152 }, 153 154 #[error( 155 "error-blahg-process-10 Image dimensions too small: {width}x{height}, minimum is 512x512" 156 )] 157 /// Badge image dimensions are below minimum requirements. 158 ProcessImageTooSmall { 159 /// The actual width of the image. 160 width: u32, 161 /// The actual height of the image. 162 height: u32, 163 }, 164 165 #[error("error-blahg-process-11 Image width too small after resize: {width}, minimum is 512")] 166 /// Badge image width is below minimum after resize. 167 ProcessImageWidthTooSmall { 168 /// The actual width after resize. 169 width: u32, 170 }, 171 172 #[error("error-blahg-process-12 No signatures field found in record")] 173 /// Badge record is missing required signatures field. 174 ProcessNoSignaturesField, 175 176 #[error("error-blahg-process-13 Missing issuer field in signature")] 177 /// Signature is missing required issuer field. 178 ProcessMissingIssuerField, 179 180 #[error("error-blahg-process-14 Missing signature field in signature")] 181 /// Signature object is missing required signature field. 182 ProcessMissingSignatureField, 183 184 #[error("error-blahg-process-15 Record serialization failed: {details}")] 185 /// Failed to serialize record for signature verification. 186 ProcessRecordSerializationFailed { 187 /// Details about the serialization error. 188 details: String, 189 }, 190 191 #[error("error-blahg-process-16 Signature decoding failed: {details}")] 192 /// Failed to decode signature bytes. 193 ProcessSignatureDecodingFailed { 194 /// Details about the decoding error. 195 details: String, 196 }, 197 198 #[error( 199 "error-blahg-process-17 Cryptographic validation failed for issuer {issuer}: {details}" 200 )] 201 /// Cryptographic signature validation failed. 202 ProcessCryptographicValidationFailed { 203 /// The issuer DID whose signature validation failed. 204 issuer: String, 205 /// Detailed error information. 206 details: String, 207 }, 208 209 // Storage domain - Database and storage errors 210 #[error("error-blahg-storage-1 Database operation failed: {operation}")] 211 /// Database operation failed. 212 StorageDatabaseFailed { 213 /// Description of the failed operation. 214 operation: String, 215 }, 216 217 #[error("error-blahg-storage-2 File storage operation failed: {operation}")] 218 /// File storage operation failed. 219 StorageFileOperationFailed { 220 /// Description of the failed file operation. 221 operation: String, 222 }, 223} 224 225/// Result type alias for convenience 226pub type Result<T> = std::result::Result<T, BlahgError>; 227 228impl From<sqlx::Error> for BlahgError { 229 fn from(err: sqlx::Error) -> Self { 230 BlahgError::StorageDatabaseFailed { 231 operation: err.to_string(), 232 } 233 } 234} 235 236impl From<serde_json::Error> for BlahgError { 237 fn from(err: serde_json::Error) -> Self { 238 BlahgError::ProcessEventHandlingFailed { 239 event_type: err.to_string(), 240 } 241 } 242} 243 244impl From<reqwest::Error> for BlahgError { 245 fn from(err: reqwest::Error) -> Self { 246 BlahgError::ProcessBadgeFetchFailed { 247 uri: err.to_string(), 248 } 249 } 250} 251 252impl From<image::ImageError> for BlahgError { 253 fn from(err: image::ImageError) -> Self { 254 BlahgError::ProcessImageDownloadFailed { 255 image_ref: err.to_string(), 256 } 257 } 258} 259 260impl From<minijinja::Error> for BlahgError { 261 fn from(err: minijinja::Error) -> Self { 262 BlahgError::HttpTemplateRenderFailed { 263 template: err.to_string(), 264 } 265 } 266} 267 268impl From<std::io::Error> for BlahgError { 269 fn from(err: std::io::Error) -> Self { 270 BlahgError::ProcessImageDownloadFailed { 271 image_ref: err.to_string(), 272 } 273 } 274} 275 276impl From<std::num::ParseIntError> for BlahgError { 277 fn from(err: std::num::ParseIntError) -> Self { 278 BlahgError::ConfigHttpPortInvalid { 279 port: err.to_string(), 280 } 281 } 282} 283 284impl From<anyhow::Error> for BlahgError { 285 fn from(err: anyhow::Error) -> Self { 286 BlahgError::ProcessEventHandlingFailed { 287 event_type: err.to_string(), 288 } 289 } 290} 291 292impl From<atproto_record::errors::AturiError> for BlahgError { 293 fn from(err: atproto_record::errors::AturiError) -> Self { 294 BlahgError::ProcessInvalidAturi { 295 uri: err.to_string(), 296 } 297 } 298} 299 300impl IntoResponse for BlahgError { 301 fn into_response(self) -> Response { 302 tracing::error!(error = ?self, "internal server error"); 303 (StatusCode::INTERNAL_SERVER_ERROR).into_response() 304 } 305}