use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;
use fortress_core::error::FortressError;
pub type ServerResult<T> = Result<T, ServerError>;
#[derive(Error, Debug)]
pub enum ServerError {
#[error("Fortress core error: {0}")]
Core(#[from] FortressError),
#[error("Authentication failed: {0}")]
Authentication(String),
#[error("Access denied: {0}")]
Authorization(String),
#[error("Validation failed: {0}")]
Validation(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Resource conflict: {0}")]
Conflict(String),
#[error("Rate limit exceeded")]
RateLimit,
#[error("Request blocked by DDoS protection")]
DdosBlocked,
#[error("Quota exceeded: {0}")]
QuotaExceeded(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Network error: {0}")]
Network(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error("Internal server error: {0}")]
Internal(String),
#[error("Service unavailable: {0}")]
Unavailable(String),
#[error("Request timeout")]
Timeout,
#[error("Payload too large: {0}")]
PayloadTooLarge(String),
}
impl ServerError {
pub fn status_code(&self) -> StatusCode {
match self {
ServerError::Authentication(_) => StatusCode::UNAUTHORIZED,
ServerError::Authorization(_) => StatusCode::FORBIDDEN,
ServerError::Validation(_) => StatusCode::BAD_REQUEST,
ServerError::NotFound(_) => StatusCode::NOT_FOUND,
ServerError::Conflict(_) => StatusCode::CONFLICT,
ServerError::RateLimit => StatusCode::TOO_MANY_REQUESTS,
ServerError::DdosBlocked => StatusCode::TOO_MANY_REQUESTS,
ServerError::QuotaExceeded(_) => StatusCode::PAYLOAD_TOO_LARGE,
ServerError::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE,
ServerError::Timeout => StatusCode::REQUEST_TIMEOUT,
ServerError::Unavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
ServerError::Core(core_err) => match core_err {
FortressError::Encryption { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::KeyManagement { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Storage { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Configuration { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Cluster { .. } => StatusCode::SERVICE_UNAVAILABLE,
FortressError::QueryExecution { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Validation { .. } => StatusCode::BAD_REQUEST,
FortressError::Io { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Network { .. } => StatusCode::INTERNAL_SERVER_ERROR,
FortressError::Authentication { .. } => StatusCode::UNAUTHORIZED,
_ => StatusCode::INTERNAL_SERVER_ERROR,
},
ServerError::Serialization(_)
| ServerError::Network(_)
| ServerError::Configuration(_)
| ServerError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
pub fn error_code(&self) -> &'static str {
match self {
ServerError::Authentication(_) => "AUTHENTICATION_FAILED",
ServerError::Authorization(_) => "ACCESS_DENIED",
ServerError::Validation(_) => "VALIDATION_FAILED",
ServerError::NotFound(_) => "NOT_FOUND",
ServerError::Conflict(_) => "CONFLICT",
ServerError::RateLimit => "RATE_LIMIT_EXCEEDED",
ServerError::DdosBlocked => "DDOS_BLOCKED",
ServerError::QuotaExceeded(_) => "QUOTA_EXCEEDED",
ServerError::PayloadTooLarge(_) => "PAYLOAD_TOO_LARGE",
ServerError::Timeout => "TIMEOUT",
ServerError::Unavailable(_) => "SERVICE_UNAVAILABLE",
ServerError::Serialization(_) => "SERIALIZATION_ERROR",
ServerError::Network(_) => "NETWORK_ERROR",
ServerError::Configuration(_) => "CONFIGURATION_ERROR",
ServerError::Internal(_) => "INTERNAL_ERROR",
ServerError::Core(_) => "CORE_ERROR",
}
}
pub fn should_log_error(&self) -> bool {
matches!(
self,
ServerError::Internal(_)
| ServerError::Core(_)
| ServerError::Network(_)
| ServerError::Configuration(_)
| ServerError::Unavailable(_)
)
}
pub fn is_client_error(&self) -> bool {
let status = self.status_code();
status.is_client_error()
}
pub fn is_server_error(&self) -> bool {
let status = self.status_code();
status.is_server_error()
}
}
impl IntoResponse for ServerError {
fn into_response(self) -> Response {
let status = self.status_code();
let error_code = self.error_code();
let message = crate::handlers::sanitize_error(&self);
let error_response = json!({
"error": {
"code": error_code,
"message": message,
"timestamp": chrono::Utc::now().to_rfc3339(),
"status": status.as_u16()
}
});
tracing::error!(
error_code = error_code,
status = status.as_u16(),
message = %self, "Server error occurred"
);
(status, Json(error_response)).into_response()
}
}
impl ServerError {
pub fn auth<S: Into<String>>(message: S) -> Self {
Self::Authentication(message.into())
}
pub fn access_denied<S: Into<String>>(message: S) -> Self {
Self::Authorization(message.into())
}
pub fn validation<S: Into<String>>(message: S) -> Self {
Self::Validation(message.into())
}
pub fn not_found<S: Into<String>>(resource: S) -> Self {
Self::NotFound(resource.into())
}
pub fn conflict<S: Into<String>>(message: S) -> Self {
Self::Conflict(message.into())
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal(message.into())
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Configuration(message.into())
}
pub fn network<S: Into<String>>(message: S) -> Self {
Self::Network(message.into())
}
pub fn serialization<S: Into<String>>(message: S) -> Self {
Self::Serialization(message.into())
}
pub fn quota_exceeded<S: Into<String>>(message: S) -> Self {
Self::QuotaExceeded(message.into())
}
pub fn payload_too_large<S: Into<String>>(message: S) -> Self {
Self::PayloadTooLarge(message.into())
}
pub fn unavailable<S: Into<String>>(message: S) -> Self {
Self::Unavailable(message.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_status_codes() {
assert_eq!(ServerError::auth("test").status_code(), StatusCode::UNAUTHORIZED);
assert_eq!(ServerError::access_denied("test").status_code(), StatusCode::FORBIDDEN);
assert_eq!(ServerError::validation("test").status_code(), StatusCode::BAD_REQUEST);
assert_eq!(ServerError::not_found("test").status_code(), StatusCode::NOT_FOUND);
assert_eq!(ServerError::conflict("test").status_code(), StatusCode::CONFLICT);
assert_eq!(ServerError::RateLimit.status_code(), StatusCode::TOO_MANY_REQUESTS);
assert_eq!(ServerError::Timeout.status_code(), StatusCode::REQUEST_TIMEOUT);
assert_eq!(ServerError::internal("test").status_code(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_error_codes() {
assert_eq!(ServerError::auth("test").error_code(), "AUTHENTICATION_FAILED");
assert_eq!(ServerError::access_denied("test").error_code(), "ACCESS_DENIED");
assert_eq!(ServerError::validation("test").error_code(), "VALIDATION_FAILED");
assert_eq!(ServerError::not_found("test").error_code(), "NOT_FOUND");
assert_eq!(ServerError::internal("test").error_code(), "INTERNAL_ERROR");
}
#[test]
fn test_error_classification() {
assert!(ServerError::auth("test").is_client_error());
assert!(ServerError::validation("test").is_client_error());
assert!(ServerError::not_found("test").is_client_error());
assert!(ServerError::internal("test").is_server_error());
assert!(ServerError::network("test").is_server_error());
assert!(ServerError::config("test").is_server_error());
}
#[test]
fn test_error_logging() {
assert!(ServerError::internal("test").should_log_error());
assert!(ServerError::network("test").should_log_error());
assert!(ServerError::config("test").should_log_error());
assert!(!ServerError::auth("test").should_log_error());
assert!(!ServerError::validation("test").should_log_error());
assert!(!ServerError::not_found("test").should_log_error());
}
}