Skip to main content

fastskill_core/http/
errors.rs

1//! HTTP error handling and conversion
2
3use axum::{
4    http::StatusCode,
5    response::{IntoResponse, Response},
6    Json,
7};
8use serde_json::json;
9use std::collections::HashMap;
10
11/// HTTP error types
12#[derive(Debug, Clone)]
13pub enum HttpError {
14    /// Authentication errors
15    Unauthorized(String),
16    Forbidden(String),
17
18    /// Validation errors
19    BadRequest(String),
20    ValidationError(HashMap<String, Vec<String>>),
21
22    /// Not found errors
23    NotFound(String),
24
25    /// Conflict errors
26    Conflict(String),
27
28    /// Server errors
29    InternalServerError(String),
30
31    /// Service-specific errors
32    ServiceError(String),
33
34    /// Service unavailable (e.g., missing API key for semantic search)
35    ServiceUnavailable(String),
36}
37
38impl HttpError {
39    /// Convert to HTTP status code
40    pub fn status_code(&self) -> StatusCode {
41        match self {
42            HttpError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
43            HttpError::Forbidden(_) => StatusCode::FORBIDDEN,
44            HttpError::BadRequest(_) | HttpError::ValidationError(_) => StatusCode::BAD_REQUEST,
45            HttpError::NotFound(_) => StatusCode::NOT_FOUND,
46            HttpError::Conflict(_) => StatusCode::CONFLICT,
47            HttpError::InternalServerError(_) | HttpError::ServiceError(_) => {
48                StatusCode::INTERNAL_SERVER_ERROR
49            }
50            HttpError::ServiceUnavailable(_) => StatusCode::SERVICE_UNAVAILABLE,
51        }
52    }
53
54    /// Get error code string
55    pub fn error_code(&self) -> &'static str {
56        match self {
57            HttpError::Unauthorized(_) => "UNAUTHORIZED",
58            HttpError::Forbidden(_) => "FORBIDDEN",
59            HttpError::BadRequest(_) => "BAD_REQUEST",
60            HttpError::ValidationError(_) => "VALIDATION_ERROR",
61            HttpError::NotFound(_) => "NOT_FOUND",
62            HttpError::Conflict(_) => "CONFLICT",
63            HttpError::InternalServerError(_) => "INTERNAL_SERVER_ERROR",
64            HttpError::ServiceError(_) => "SERVICE_ERROR",
65            HttpError::ServiceUnavailable(_) => "SERVICE_UNAVAILABLE",
66        }
67    }
68}
69
70impl std::fmt::Display for HttpError {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        match self {
73            HttpError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
74            HttpError::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
75            HttpError::BadRequest(msg) => write!(f, "Bad Request: {}", msg),
76            HttpError::ValidationError(errors) => {
77                write!(f, "Validation Error: {:?}", errors)
78            }
79            HttpError::NotFound(msg) => write!(f, "Not Found: {}", msg),
80            HttpError::Conflict(msg) => write!(f, "Conflict: {}", msg),
81            HttpError::InternalServerError(msg) => write!(f, "Internal Server Error: {}", msg),
82            HttpError::ServiceError(msg) => write!(f, "Service Error: {}", msg),
83            HttpError::ServiceUnavailable(msg) => write!(f, "Service Unavailable: {}", msg),
84        }
85    }
86}
87
88impl std::error::Error for HttpError {}
89
90impl IntoResponse for HttpError {
91    fn into_response(self) -> Response {
92        let status = self.status_code();
93        let error_code = self.error_code();
94
95        let (message, details) = match self {
96            HttpError::ValidationError(errors) => {
97                ("Validation failed".to_string(), Some(json!(errors)))
98            }
99            HttpError::Unauthorized(msg)
100            | HttpError::Forbidden(msg)
101            | HttpError::BadRequest(msg)
102            | HttpError::NotFound(msg)
103            | HttpError::Conflict(msg)
104            | HttpError::InternalServerError(msg)
105            | HttpError::ServiceError(msg)
106            | HttpError::ServiceUnavailable(msg) => (msg, None),
107        };
108
109        let body = Json(json!({
110            "success": false,
111            "error": {
112                "code": error_code,
113                "message": message,
114                "details": details
115            }
116        }));
117
118        (status, body).into_response()
119    }
120}
121
122/// Convert service errors to HTTP errors
123impl From<crate::core::service::ServiceError> for HttpError {
124    fn from(err: crate::core::service::ServiceError) -> Self {
125        match err {
126            crate::core::service::ServiceError::SkillNotFound(msg) => HttpError::NotFound(msg),
127            crate::core::service::ServiceError::Validation(msg) => HttpError::BadRequest(msg),
128            crate::core::service::ServiceError::Config(msg) => HttpError::InternalServerError(msg),
129            crate::core::service::ServiceError::Io(err) => {
130                HttpError::InternalServerError(err.to_string())
131            }
132            crate::core::service::ServiceError::Custom(msg) => HttpError::ServiceError(msg),
133            crate::core::service::ServiceError::Storage(msg) => HttpError::InternalServerError(msg),
134            crate::core::service::ServiceError::Execution(_) => {
135                HttpError::InternalServerError("Execution error".to_string())
136            }
137            crate::core::service::ServiceError::Event(msg) => HttpError::InternalServerError(msg),
138            crate::core::service::ServiceError::InvalidOperation(msg) => HttpError::BadRequest(msg),
139        }
140    }
141}
142
143/// Result type alias for HTTP operations
144pub type HttpResult<T> = Result<T, HttpError>;