Something went wrong. Try again.
A fork of @slices.network/slices forked from slices.network/slices
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260//! Error types for the Slices API.//!//! All error types follow the pattern: `error-slices-{domain}-{number} {message}`//! This provides consistent error tracking and debugging across the application.//!//! ## Error Hierarchy//!//! ```text//! AppError (HTTP boundary layer)//! ├─> DatabaseError (data layer)//! ├─> SyncError (sync service)//! ├─> JetstreamError (real-time processing)//! ├─> ActorResolverError (identity resolution)//! └─> BlobUploadError (ATProto extensions)//! ```
use axum::{ Json, http::StatusCode, response::{IntoResponse, Response},};use thiserror::Error;
// =============================================================================// Database Layer Errors// =============================================================================
/// Database operation errors from the data access layer.#[derive(Error, Debug)]pub enum DatabaseError { #[error("error-slices-database-1 SQL query failed: {0}")] SqlQuery(#[from] sqlx::Error),
#[error("error-slices-database-2 Record not found: {uri}")] RecordNotFound { uri: String },}
// =============================================================================// Sync Service Errors// =============================================================================
/// Errors from background sync operations with ATProto relay.#[derive(Error, Debug)]pub enum SyncError { #[error("error-slices-sync-1 HTTP request failed: {0}")] HttpRequest(#[from] reqwest::Error),
#[error("error-slices-sync-2 Database operation failed: {0}")] Database(#[from] DatabaseError),
#[error("error-slices-sync-3 JSON parsing failed: {0}")] JsonParse(#[from] serde_json::Error),
#[error("error-slices-sync-4 Failed to list repos for collection: {status}")] ListRepos { status: u16 },
#[error("error-slices-sync-5 Failed to list records: {status}")] ListRecords { status: u16 },
#[error("error-slices-sync-6 Task join failed: {0}")] TaskJoin(#[from] tokio::task::JoinError),
#[error("error-slices-sync-7 Generic error: {0}")] Generic(String),}
// =============================================================================// Jetstream / Real-time Processing Errors// =============================================================================
/// Errors from Jetstream event stream processing.#[derive(Error, Debug)]pub enum JetstreamError { #[error("error-slices-jetstream-1 Connection failed: {message}")] ConnectionFailed { message: String },
#[error("error-slices-jetstream-2 Database error: {0}")] Database(#[from] DatabaseError),}
// =============================================================================// ATProto Identity Resolution Errors// =============================================================================
/// Errors from DID and handle resolution operations.#[derive(Error, Debug)]pub enum ActorResolverError { #[error("error-slice-actor-1 Failed to resolve DID: {0}")] ResolveFailed(String),
#[error("error-slice-actor-2 Failed to parse DID: {0}")] ParseFailed(String),
#[error("error-slice-actor-3 Subject resolved to handle instead of DID")] InvalidSubject,}
// =============================================================================// ATProto Extensions Errors// =============================================================================
/// Errors from ATProto blob upload operations.#[derive(Error, Debug)]pub enum BlobUploadError { #[error("error-slice-blob-1 HTTP request failed: {0}")] HttpRequest(#[from] reqwest_middleware::Error),
#[error("error-slice-blob-2 JSON parsing failed: {0}")] JsonParse(#[from] serde_json::Error),
#[error("error-slice-blob-3 DPoP proof creation failed: {0}")] DPoPProof(String),
#[error("error-slice-blob-4 Upload request failed: {status} - {message}")] UploadFailed { status: u16, message: String },}
// =============================================================================// Core Application Errors (HTTP Boundary Layer)// =============================================================================
/// Top-level application errors for HTTP handlers and server operations.////// This is the boundary layer that converts domain errors into HTTP responses./// Domain-specific errors are wrapped and converted to appropriate HTTP status codes.#[derive(Error, Debug)]pub enum AppError { #[error("error-slices-app-1 Database error: {0}")] Database(#[from] DatabaseError),
#[error("error-slices-app-2 Sync error: {0}")] Sync(#[from] SyncError),
#[error("error-slices-app-3 Jetstream error: {0}")] Jetstream(#[from] JetstreamError),
#[error("error-slices-app-4 Actor resolution error: {0}")] ActorResolver(#[from] ActorResolverError),
#[error("error-slices-app-5 Blob upload error: {0}")] BlobUpload(#[from] BlobUploadError),
#[error("error-slices-app-6 Database connection failed: {0}")] DatabaseConnection(#[from] sqlx::Error),
#[error("error-slices-app-7 Database migration failed: {0}")] Migration(#[from] sqlx::migrate::MigrateError),
#[error("error-slices-app-8 Server bind failed: {0}")] ServerBind(#[from] std::io::Error),
#[error("error-slices-app-9 Cache error: {0}")] Cache(#[from] anyhow::Error),
#[error("error-slices-app-10 Bad request: {0}")] BadRequest(String),
#[error("error-slices-app-11 Resource not found: {0}")] NotFound(String),
#[error("error-slices-app-12 Authentication required: {0}")] AuthRequired(String),
#[error("error-slices-app-13 Forbidden: {0}")] Forbidden(String),
#[error("error-slices-app-14 Internal server error: {0}")] Internal(String),}
impl From<StatusCode> for AppError { fn from(status: StatusCode) -> Self { match status { StatusCode::BAD_REQUEST => AppError::BadRequest("Bad request".to_string()), StatusCode::UNAUTHORIZED => { AppError::AuthRequired("Authentication required".to_string()) } StatusCode::FORBIDDEN => AppError::Forbidden("Forbidden".to_string()), StatusCode::NOT_FOUND => AppError::NotFound("Not found".to_string()), _ => AppError::Internal(format!("HTTP error: {}", status)), } }}
impl IntoResponse for AppError { fn into_response(self) -> Response { let (status, error_name, error_message) = match &self { AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, "BadRequest", msg.clone()), AppError::NotFound(msg) => (StatusCode::NOT_FOUND, "NotFound", msg.clone()), AppError::AuthRequired(msg) => ( StatusCode::UNAUTHORIZED, "AuthenticationRequired", msg.clone(), ), AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, "Forbidden", msg.clone()), AppError::Internal(msg) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", msg.clone(), ),
// Domain errors - all map to internal server error AppError::Database(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::Sync(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::Jetstream(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::ActorResolver(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::BlobUpload(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ),
// Infrastructure errors AppError::DatabaseConnection(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::Migration(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::ServerBind(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), AppError::Cache(e) => ( StatusCode::INTERNAL_SERVER_ERROR, "InternalServerError", e.to_string(), ), };
let body = Json(serde_json::json!({ "error": error_name, "message": error_message }));
(status, body).into_response() }}