An ATProtocol powered blogging engine.
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}