//! Error types for the StemeDB API. use axum::{ http::StatusCode, response::{IntoResponse, Response}, Json, }; use thiserror::Error; use crate::dto::ErrorResponse; /// API-level errors. #[derive(Debug, Error)] pub enum ApiError { /// Invalid hex encoding in request. #[error("Invalid hex encoding: {0}")] InvalidHex(String), /// Hash has incorrect byte length. #[error("Invalid hash length: expected {expected}, got {actual}")] InvalidHashLength { /// Expected length in bytes. expected: usize, /// Actual length in bytes. actual: usize, }, /// Write-ahead log error. #[error("WAL error: {0}")] Wal(#[from] stemedb_wal::QuarantineError), /// Storage backend error. #[error("Storage error: {0}")] Storage(#[from] stemedb_storage::StorageError), /// Serialization/deserialization error. #[error("Serialization error: {0}")] Serialization(String), /// Ingestion pipeline error. #[error("Ingest error: {0}")] Ingest(#[from] stemedb_ingest::error::IngestError), /// Query execution error. #[error("Query error: {0}")] Query(#[from] stemedb_query::QueryError), /// Invalid request parameters. #[error("Invalid request: {0}")] InvalidRequest(String), /// Resource not found. #[error("Not found: {0}")] NotFound(String), /// Resource conflict (e.g., already superseded). #[error("Conflict: {0}")] Conflict(String), /// Internal server error. #[error("Internal error: {0}")] Internal(String), /// Missing or invalid API key. #[error("Unauthorized: {0}")] Unauthorized(String), /// Valid API key but insufficient permissions. #[error("Forbidden: {0}")] Forbidden(String), /// Rate limit exceeded. #[error("Rate limit exceeded: {0}")] RateLimited(String), } impl IntoResponse for ApiError { fn into_response(self) -> Response { let (status, code, message) = match self { ApiError::InvalidHex(ref msg) => (StatusCode::BAD_REQUEST, "INVALID_HEX", msg.clone()), ApiError::InvalidHashLength { .. } => { (StatusCode::BAD_REQUEST, "INVALID_HASH_LENGTH", self.to_string()) } ApiError::InvalidRequest(ref msg) => { (StatusCode::BAD_REQUEST, "INVALID_REQUEST", msg.clone()) } ApiError::NotFound(ref msg) => (StatusCode::NOT_FOUND, "NOT_FOUND", msg.clone()), ApiError::Wal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "WAL_ERROR", self.to_string()), ApiError::Storage(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "STORAGE_ERROR", self.to_string()) } ApiError::Serialization(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "SERIALIZATION_ERROR", self.to_string()) } ApiError::Ingest(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "INGEST_ERROR", self.to_string()) } ApiError::Query(_) => { (StatusCode::INTERNAL_SERVER_ERROR, "QUERY_ERROR", self.to_string()) } ApiError::Conflict(ref msg) => (StatusCode::CONFLICT, "CONFLICT", msg.clone()), ApiError::Internal(ref msg) => { (StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", msg.clone()) } ApiError::Unauthorized(ref msg) => { (StatusCode::UNAUTHORIZED, "UNAUTHORIZED", msg.clone()) } ApiError::Forbidden(ref msg) => (StatusCode::FORBIDDEN, "FORBIDDEN", msg.clone()), ApiError::RateLimited(ref msg) => { (StatusCode::TOO_MANY_REQUESTS, "RATE_LIMITED", msg.clone()) } }; let error_response = ErrorResponse { error: message, code: code.to_string() }; (status, Json(error_response)).into_response() } } /// Result type for API operations. pub type Result = std::result::Result;