Highly ambitious ATProtocol AppView service and sdks
at main 9.2 kB view raw
1//! Error types for the Slices API. 2//! 3//! All error types follow the pattern: `error-slices-{domain}-{number} {message}` 4//! This provides consistent error tracking and debugging across the application. 5//! 6//! ## Error Hierarchy 7//! 8//! ```text 9//! AppError (HTTP boundary layer) 10//! ├─> DatabaseError (data layer) 11//! ├─> SyncError (sync service) 12//! ├─> JetstreamError (real-time processing) 13//! ├─> ActorResolverError (identity resolution) 14//! └─> BlobUploadError (ATProto extensions) 15//! ``` 16 17use axum::{ 18 Json, 19 http::StatusCode, 20 response::{IntoResponse, Response}, 21}; 22use thiserror::Error; 23 24// ============================================================================= 25// Database Layer Errors 26// ============================================================================= 27 28/// Database operation errors from the data access layer. 29#[derive(Error, Debug)] 30pub enum DatabaseError { 31 #[error("error-slices-database-1 SQL query failed: {0}")] 32 SqlQuery(#[from] sqlx::Error), 33 34 #[error("error-slices-database-2 Record not found: {uri}")] 35 RecordNotFound { uri: String }, 36} 37 38// ============================================================================= 39// Sync Service Errors 40// ============================================================================= 41 42/// Errors from background sync operations with ATProto relay. 43#[derive(Error, Debug)] 44pub enum SyncError { 45 #[error("error-slices-sync-1 HTTP request failed: {0}")] 46 HttpRequest(#[from] reqwest::Error), 47 48 #[error("error-slices-sync-2 Database operation failed: {0}")] 49 Database(#[from] DatabaseError), 50 51 #[error("error-slices-sync-3 JSON parsing failed: {0}")] 52 JsonParse(#[from] serde_json::Error), 53 54 #[error("error-slices-sync-4 Failed to list repos for collection: {status}")] 55 ListRepos { status: u16 }, 56 57 #[error("error-slices-sync-5 Failed to list records: {status}")] 58 ListRecords { status: u16 }, 59 60 #[error("error-slices-sync-6 Task join failed: {0}")] 61 TaskJoin(#[from] tokio::task::JoinError), 62 63 #[error("error-slices-sync-7 Generic error: {0}")] 64 Generic(String), 65 66 #[error("error-slices-sync-8 Database query failed: {0}")] 67 DatabaseQuery(String), 68 69 #[error("error-slices-sync-9 Job cancelled by user")] 70 Cancelled, 71} 72 73// ============================================================================= 74// Jetstream / Real-time Processing Errors 75// ============================================================================= 76 77/// Errors from Jetstream event stream processing. 78#[derive(Error, Debug)] 79pub enum JetstreamError { 80 #[error("error-slices-jetstream-1 Connection failed: {message}")] 81 ConnectionFailed { message: String }, 82 83 #[error("error-slices-jetstream-2 Database error: {0}")] 84 Database(#[from] DatabaseError), 85} 86 87// ============================================================================= 88// ATProto Identity Resolution Errors 89// ============================================================================= 90 91/// Errors from DID and handle resolution operations. 92#[derive(Error, Debug)] 93pub enum ActorResolverError { 94 #[error("error-slice-actor-1 Failed to resolve DID: {0}")] 95 ResolveFailed(String), 96 97 #[error("error-slice-actor-2 Failed to parse DID: {0}")] 98 ParseFailed(String), 99 100 #[error("error-slice-actor-3 Subject resolved to handle instead of DID")] 101 InvalidSubject, 102} 103 104// ============================================================================= 105// ATProto Extensions Errors 106// ============================================================================= 107 108/// Errors from ATProto blob upload operations. 109#[derive(Error, Debug)] 110pub enum BlobUploadError { 111 #[error("error-slice-blob-1 HTTP request failed: {0}")] 112 HttpRequest(#[from] reqwest_middleware::Error), 113 114 #[error("error-slice-blob-2 JSON parsing failed: {0}")] 115 JsonParse(#[from] serde_json::Error), 116 117 #[error("error-slice-blob-3 DPoP proof creation failed: {0}")] 118 DPoPProof(String), 119 120 #[error("error-slice-blob-4 Upload request failed: {status} - {message}")] 121 UploadFailed { status: u16, message: String }, 122} 123 124// ============================================================================= 125// Core Application Errors (HTTP Boundary Layer) 126// ============================================================================= 127 128/// Top-level application errors for HTTP handlers and server operations. 129/// 130/// This is the boundary layer that converts domain errors into HTTP responses. 131/// Domain-specific errors are wrapped and converted to appropriate HTTP status codes. 132#[derive(Error, Debug)] 133pub enum AppError { 134 #[error("error-slices-app-1 Database error: {0}")] 135 Database(#[from] DatabaseError), 136 137 #[error("error-slices-app-2 Sync error: {0}")] 138 Sync(#[from] SyncError), 139 140 #[error("error-slices-app-3 Jetstream error: {0}")] 141 Jetstream(#[from] JetstreamError), 142 143 #[error("error-slices-app-4 Actor resolution error: {0}")] 144 ActorResolver(#[from] ActorResolverError), 145 146 #[error("error-slices-app-5 Blob upload error: {0}")] 147 BlobUpload(#[from] BlobUploadError), 148 149 #[error("error-slices-app-6 Database connection failed: {0}")] 150 DatabaseConnection(#[from] sqlx::Error), 151 152 #[error("error-slices-app-7 Database migration failed: {0}")] 153 Migration(#[from] sqlx::migrate::MigrateError), 154 155 #[error("error-slices-app-8 Server bind failed: {0}")] 156 ServerBind(#[from] std::io::Error), 157 158 #[error("error-slices-app-9 Cache error: {0}")] 159 Cache(#[from] anyhow::Error), 160 161 #[error("error-slices-app-10 Bad request: {0}")] 162 BadRequest(String), 163 164 #[error("error-slices-app-11 Resource not found: {0}")] 165 NotFound(String), 166 167 #[error("error-slices-app-12 Authentication required: {0}")] 168 AuthRequired(String), 169 170 #[error("error-slices-app-13 Forbidden: {0}")] 171 Forbidden(String), 172 173 #[error("error-slices-app-14 Internal server error: {0}")] 174 Internal(String), 175} 176 177impl From<StatusCode> for AppError { 178 fn from(status: StatusCode) -> Self { 179 match status { 180 StatusCode::BAD_REQUEST => AppError::BadRequest("Bad request".to_string()), 181 StatusCode::UNAUTHORIZED => { 182 AppError::AuthRequired("Authentication required".to_string()) 183 } 184 StatusCode::FORBIDDEN => AppError::Forbidden("Forbidden".to_string()), 185 StatusCode::NOT_FOUND => AppError::NotFound("Not found".to_string()), 186 _ => AppError::Internal(format!("HTTP error: {}", status)), 187 } 188 } 189} 190 191impl IntoResponse for AppError { 192 fn into_response(self) -> Response { 193 let (status, error_name, error_message) = match &self { 194 AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, "BadRequest", msg.clone()), 195 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, "NotFound", msg.clone()), 196 AppError::AuthRequired(msg) => ( 197 StatusCode::UNAUTHORIZED, 198 "AuthenticationRequired", 199 msg.clone(), 200 ), 201 AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, "Forbidden", msg.clone()), 202 AppError::Internal(msg) => ( 203 StatusCode::INTERNAL_SERVER_ERROR, 204 "InternalServerError", 205 msg.clone(), 206 ), 207 208 // Domain errors - all map to internal server error 209 AppError::Database(e) => ( 210 StatusCode::INTERNAL_SERVER_ERROR, 211 "InternalServerError", 212 e.to_string(), 213 ), 214 AppError::Sync(e) => ( 215 StatusCode::INTERNAL_SERVER_ERROR, 216 "InternalServerError", 217 e.to_string(), 218 ), 219 AppError::Jetstream(e) => ( 220 StatusCode::INTERNAL_SERVER_ERROR, 221 "InternalServerError", 222 e.to_string(), 223 ), 224 AppError::ActorResolver(e) => ( 225 StatusCode::INTERNAL_SERVER_ERROR, 226 "InternalServerError", 227 e.to_string(), 228 ), 229 AppError::BlobUpload(e) => ( 230 StatusCode::INTERNAL_SERVER_ERROR, 231 "InternalServerError", 232 e.to_string(), 233 ), 234 235 // Infrastructure errors 236 AppError::DatabaseConnection(e) => ( 237 StatusCode::INTERNAL_SERVER_ERROR, 238 "InternalServerError", 239 e.to_string(), 240 ), 241 AppError::Migration(e) => ( 242 StatusCode::INTERNAL_SERVER_ERROR, 243 "InternalServerError", 244 e.to_string(), 245 ), 246 AppError::ServerBind(e) => ( 247 StatusCode::INTERNAL_SERVER_ERROR, 248 "InternalServerError", 249 e.to_string(), 250 ), 251 AppError::Cache(e) => ( 252 StatusCode::INTERNAL_SERVER_ERROR, 253 "InternalServerError", 254 e.to_string(), 255 ), 256 }; 257 258 let body = Json(serde_json::json!({ 259 "error": error_name, 260 "message": error_message 261 })); 262 263 (status, body).into_response() 264 } 265}