use axum::response::{IntoResponse, Response}; use reqwest::StatusCode; use thiserror::Error; /// Main error type for the Blahg application /// /// All errors follow the format: error-blahg-- :
/// Domains are organized alphabetically: config, consumer, http, process, storage #[derive(Error, Debug)] pub enum BlahgError { // Config domain - Configuration-related errors #[error("error-blahg-config-1 Failed to parse HTTP client timeout: {details}")] /// Failed to parse HTTP client timeout from environment variable. ConfigHttpTimeoutInvalid { /// Details about the parsing error. details: String, }, #[error("error-blahg-config-2 Failed to parse HTTP port: {port}")] /// Failed to parse HTTP port from environment variable. ConfigHttpPortInvalid { /// The invalid port value. port: String, }, #[error("error-blahg-config-3 Invalid S3 URL format: {details}")] /// Failed to parse S3 URL format from environment variable. ConfigS3UrlInvalid { /// Details about the S3 URL parsing error. details: String, }, #[error("error-blahg-config-4 Required feature not enabled: {feature}")] /// Required feature is not enabled in the build. ConfigFeatureNotEnabled { /// The feature that is required but not enabled. feature: String, }, // Consumer domain - Jetstream consumer errors /// Error when a DNS nameserver IP cannot be parsed. /// /// This error occurs when the DNS_NAMESERVERS environment variable contains /// an IP address that cannot be parsed as a valid IpAddr. #[error("error-blahg-config-5 Unable to parse nameserver IP '{0}': {1}")] NameserverParsingFailed(String, std::net::AddrParseError), // Consumer domain - Jetstream consumer errors #[error("error-blahg-consumer-1 Failed to send badge event to queue: {details}")] /// Failed to send badge event to the processing queue. ConsumerQueueSendFailed { /// Details about the send failure. details: String, }, #[error("error-blahg-consumer-2 Jetstream disconnected: {details}")] /// Jetstream consumer disconnected. ConsumerDisconnected { /// Details about the disconnection. details: String, }, #[error( "error-blahg-consumer-3 Jetstream disconnect rate exceeded: {disconnect_count} disconnects in {duration_mins} minutes" )] /// Jetstream consumer disconnect rate exceeded the allowable limit. ConsumerDisconnectRateExceeded { /// Number of disconnects in the time period. disconnect_count: usize, /// Duration in minutes for the disconnect rate calculation. duration_mins: u64, }, // HTTP domain - HTTP server and template errors #[error("error-blahg-http-1 Template rendering failed: {template}")] /// Template rendering failed in HTTP response. HttpTemplateRenderFailed { /// The template that failed to render. template: String, }, #[error("error-blahg-http-2 Internal server error")] /// Generic internal server error for HTTP responses. HttpInternalServerError, // Process domain - Badge processing errors #[error("error-blahg-process-1 Invalid AT-URI format: {uri}")] /// Invalid AT-URI format encountered during processing. ProcessInvalidAturi { /// The invalid URI string. uri: String, }, #[error("error-blahg-process-2 Failed to resolve identity for {did}: {details}")] /// Identity resolution failed with detailed error information. ProcessIdentityResolutionFailed { /// The DID that could not be resolved. did: String, /// Detailed error information. details: String, }, #[error("error-blahg-process-3 Failed to fetch badge record: {uri}")] /// Failed to fetch badge record from AT Protocol. ProcessBadgeFetchFailed { /// The badge URI that could not be fetched. uri: String, }, #[error("error-blahg-process-4 Failed to fetch badge {uri}: {details}")] /// Badge record fetch failed with detailed error information. ProcessBadgeRecordFetchFailed { /// The badge URI that could not be fetched. uri: String, /// Detailed error information. details: String, }, #[error("error-blahg-process-5 Failed to download badge image: {image_ref}")] /// Failed to download badge image. ProcessImageDownloadFailed { /// Reference to the image that failed to download. image_ref: String, }, #[error("error-blahg-process-6 Failed to process badge event: {event_type}")] /// Failed to process a badge event from Jetstream. ProcessEventHandlingFailed { /// Type of event that failed to process. event_type: String, }, #[error("error-blahg-process-7 Image file too large: {size} bytes exceeds 3MB limit")] /// Badge image file exceeds maximum allowed size. ProcessImageTooLarge { /// The actual size of the image in bytes. size: usize, }, #[error("error-blahg-process-8 Failed to decode image: {details}")] /// Failed to decode badge image data. ProcessImageDecodeFailed { /// Details about the decode error. details: String, }, #[error("error-blahg-process-9 Unsupported image format: {format}")] /// Badge image is in an unsupported format. ProcessUnsupportedImageFormat { /// The unsupported image format. format: String, }, #[error( "error-blahg-process-10 Image dimensions too small: {width}x{height}, minimum is 512x512" )] /// Badge image dimensions are below minimum requirements. ProcessImageTooSmall { /// The actual width of the image. width: u32, /// The actual height of the image. height: u32, }, #[error("error-blahg-process-11 Image width too small after resize: {width}, minimum is 512")] /// Badge image width is below minimum after resize. ProcessImageWidthTooSmall { /// The actual width after resize. width: u32, }, #[error("error-blahg-process-12 No signatures field found in record")] /// Badge record is missing required signatures field. ProcessNoSignaturesField, #[error("error-blahg-process-13 Missing issuer field in signature")] /// Signature is missing required issuer field. ProcessMissingIssuerField, #[error("error-blahg-process-14 Missing signature field in signature")] /// Signature object is missing required signature field. ProcessMissingSignatureField, #[error("error-blahg-process-15 Record serialization failed: {details}")] /// Failed to serialize record for signature verification. ProcessRecordSerializationFailed { /// Details about the serialization error. details: String, }, #[error("error-blahg-process-16 Signature decoding failed: {details}")] /// Failed to decode signature bytes. ProcessSignatureDecodingFailed { /// Details about the decoding error. details: String, }, #[error( "error-blahg-process-17 Cryptographic validation failed for issuer {issuer}: {details}" )] /// Cryptographic signature validation failed. ProcessCryptographicValidationFailed { /// The issuer DID whose signature validation failed. issuer: String, /// Detailed error information. details: String, }, // Storage domain - Database and storage errors #[error("error-blahg-storage-1 Database operation failed: {operation}")] /// Database operation failed. StorageDatabaseFailed { /// Description of the failed operation. operation: String, }, #[error("error-blahg-storage-2 File storage operation failed: {operation}")] /// File storage operation failed. StorageFileOperationFailed { /// Description of the failed file operation. operation: String, }, } /// Result type alias for convenience pub type Result = std::result::Result; impl From for BlahgError { fn from(err: sqlx::Error) -> Self { BlahgError::StorageDatabaseFailed { operation: err.to_string(), } } } impl From for BlahgError { fn from(err: serde_json::Error) -> Self { BlahgError::ProcessEventHandlingFailed { event_type: err.to_string(), } } } impl From for BlahgError { fn from(err: reqwest::Error) -> Self { BlahgError::ProcessBadgeFetchFailed { uri: err.to_string(), } } } impl From for BlahgError { fn from(err: image::ImageError) -> Self { BlahgError::ProcessImageDownloadFailed { image_ref: err.to_string(), } } } impl From for BlahgError { fn from(err: minijinja::Error) -> Self { BlahgError::HttpTemplateRenderFailed { template: err.to_string(), } } } impl From for BlahgError { fn from(err: std::io::Error) -> Self { BlahgError::ProcessImageDownloadFailed { image_ref: err.to_string(), } } } impl From for BlahgError { fn from(err: std::num::ParseIntError) -> Self { BlahgError::ConfigHttpPortInvalid { port: err.to_string(), } } } impl From for BlahgError { fn from(err: anyhow::Error) -> Self { BlahgError::ProcessEventHandlingFailed { event_type: err.to_string(), } } } impl From for BlahgError { fn from(err: atproto_record::errors::AturiError) -> Self { BlahgError::ProcessInvalidAturi { uri: err.to_string(), } } } impl IntoResponse for BlahgError { fn into_response(self) -> Response { tracing::error!(error = ?self, "internal server error"); (StatusCode::INTERNAL_SERVER_ERROR).into_response() } }