Highly ambitious ATProtocol AppView service and sdks
at main 9.0 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 67// ============================================================================= 68// Jetstream / Real-time Processing Errors 69// ============================================================================= 70 71/// Errors from Jetstream event stream processing. 72#[derive(Error, Debug)] 73pub enum JetstreamError { 74 #[error("error-slices-jetstream-1 Connection failed: {message}")] 75 ConnectionFailed { message: String }, 76 77 #[error("error-slices-jetstream-2 Database error: {0}")] 78 Database(#[from] DatabaseError), 79} 80 81// ============================================================================= 82// ATProto Identity Resolution Errors 83// ============================================================================= 84 85/// Errors from DID and handle resolution operations. 86#[derive(Error, Debug)] 87pub enum ActorResolverError { 88 #[error("error-slice-actor-1 Failed to resolve DID: {0}")] 89 ResolveFailed(String), 90 91 #[error("error-slice-actor-2 Failed to parse DID: {0}")] 92 ParseFailed(String), 93 94 #[error("error-slice-actor-3 Subject resolved to handle instead of DID")] 95 InvalidSubject, 96} 97 98// ============================================================================= 99// ATProto Extensions Errors 100// ============================================================================= 101 102/// Errors from ATProto blob upload operations. 103#[derive(Error, Debug)] 104pub enum BlobUploadError { 105 #[error("error-slice-blob-1 HTTP request failed: {0}")] 106 HttpRequest(#[from] reqwest_middleware::Error), 107 108 #[error("error-slice-blob-2 JSON parsing failed: {0}")] 109 JsonParse(#[from] serde_json::Error), 110 111 #[error("error-slice-blob-3 DPoP proof creation failed: {0}")] 112 DPoPProof(String), 113 114 #[error("error-slice-blob-4 Upload request failed: {status} - {message}")] 115 UploadFailed { status: u16, message: String }, 116} 117 118// ============================================================================= 119// Core Application Errors (HTTP Boundary Layer) 120// ============================================================================= 121 122/// Top-level application errors for HTTP handlers and server operations. 123/// 124/// This is the boundary layer that converts domain errors into HTTP responses. 125/// Domain-specific errors are wrapped and converted to appropriate HTTP status codes. 126#[derive(Error, Debug)] 127pub enum AppError { 128 #[error("error-slices-app-1 Database error: {0}")] 129 Database(#[from] DatabaseError), 130 131 #[error("error-slices-app-2 Sync error: {0}")] 132 Sync(#[from] SyncError), 133 134 #[error("error-slices-app-3 Jetstream error: {0}")] 135 Jetstream(#[from] JetstreamError), 136 137 #[error("error-slices-app-4 Actor resolution error: {0}")] 138 ActorResolver(#[from] ActorResolverError), 139 140 #[error("error-slices-app-5 Blob upload error: {0}")] 141 BlobUpload(#[from] BlobUploadError), 142 143 #[error("error-slices-app-6 Database connection failed: {0}")] 144 DatabaseConnection(#[from] sqlx::Error), 145 146 #[error("error-slices-app-7 Database migration failed: {0}")] 147 Migration(#[from] sqlx::migrate::MigrateError), 148 149 #[error("error-slices-app-8 Server bind failed: {0}")] 150 ServerBind(#[from] std::io::Error), 151 152 #[error("error-slices-app-9 Cache error: {0}")] 153 Cache(#[from] anyhow::Error), 154 155 #[error("error-slices-app-10 Bad request: {0}")] 156 BadRequest(String), 157 158 #[error("error-slices-app-11 Resource not found: {0}")] 159 NotFound(String), 160 161 #[error("error-slices-app-12 Authentication required: {0}")] 162 AuthRequired(String), 163 164 #[error("error-slices-app-13 Forbidden: {0}")] 165 Forbidden(String), 166 167 #[error("error-slices-app-14 Internal server error: {0}")] 168 Internal(String), 169} 170 171impl From<StatusCode> for AppError { 172 fn from(status: StatusCode) -> Self { 173 match status { 174 StatusCode::BAD_REQUEST => AppError::BadRequest("Bad request".to_string()), 175 StatusCode::UNAUTHORIZED => { 176 AppError::AuthRequired("Authentication required".to_string()) 177 } 178 StatusCode::FORBIDDEN => AppError::Forbidden("Forbidden".to_string()), 179 StatusCode::NOT_FOUND => AppError::NotFound("Not found".to_string()), 180 _ => AppError::Internal(format!("HTTP error: {}", status)), 181 } 182 } 183} 184 185impl IntoResponse for AppError { 186 fn into_response(self) -> Response { 187 let (status, error_name, error_message) = match &self { 188 AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, "BadRequest", msg.clone()), 189 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, "NotFound", msg.clone()), 190 AppError::AuthRequired(msg) => ( 191 StatusCode::UNAUTHORIZED, 192 "AuthenticationRequired", 193 msg.clone(), 194 ), 195 AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, "Forbidden", msg.clone()), 196 AppError::Internal(msg) => ( 197 StatusCode::INTERNAL_SERVER_ERROR, 198 "InternalServerError", 199 msg.clone(), 200 ), 201 202 // Domain errors - all map to internal server error 203 AppError::Database(e) => ( 204 StatusCode::INTERNAL_SERVER_ERROR, 205 "InternalServerError", 206 e.to_string(), 207 ), 208 AppError::Sync(e) => ( 209 StatusCode::INTERNAL_SERVER_ERROR, 210 "InternalServerError", 211 e.to_string(), 212 ), 213 AppError::Jetstream(e) => ( 214 StatusCode::INTERNAL_SERVER_ERROR, 215 "InternalServerError", 216 e.to_string(), 217 ), 218 AppError::ActorResolver(e) => ( 219 StatusCode::INTERNAL_SERVER_ERROR, 220 "InternalServerError", 221 e.to_string(), 222 ), 223 AppError::BlobUpload(e) => ( 224 StatusCode::INTERNAL_SERVER_ERROR, 225 "InternalServerError", 226 e.to_string(), 227 ), 228 229 // Infrastructure errors 230 AppError::DatabaseConnection(e) => ( 231 StatusCode::INTERNAL_SERVER_ERROR, 232 "InternalServerError", 233 e.to_string(), 234 ), 235 AppError::Migration(e) => ( 236 StatusCode::INTERNAL_SERVER_ERROR, 237 "InternalServerError", 238 e.to_string(), 239 ), 240 AppError::ServerBind(e) => ( 241 StatusCode::INTERNAL_SERVER_ERROR, 242 "InternalServerError", 243 e.to_string(), 244 ), 245 AppError::Cache(e) => ( 246 StatusCode::INTERNAL_SERVER_ERROR, 247 "InternalServerError", 248 e.to_string(), 249 ), 250 }; 251 252 let body = Json(serde_json::json!({ 253 "error": error_name, 254 "message": error_message 255 })); 256 257 (status, body).into_response() 258 } 259}