Skip to main content

stynx_code_errors/
lib.rs

1use axum::http::StatusCode;
2use axum::response::{IntoResponse, Response};
3use serde_json::json;
4
5#[derive(Debug, thiserror::Error)]
6pub enum AppError {
7    #[error("Provider error: {0}")]
8    Provider(String),
9
10    #[error("Tool error: {0}")]
11    Tool(String),
12
13    #[error("Permission denied: {0}")]
14    PermissionDenied(String),
15
16    #[error("Invalid request: {0}")]
17    BadRequest(String),
18
19    #[error("Max turns ({0}) exceeded")]
20    MaxTurnsExceeded(usize),
21
22    #[error("Interrupted")]
23    Interrupted,
24
25    #[error("Unauthorized")]
26    Unauthorized,
27
28    #[error(transparent)]
29    Internal(#[from] anyhow::Error),
30}
31
32impl AppError {
33    pub fn is_interrupted(&self) -> bool {
34        matches!(self, AppError::Interrupted)
35    }
36
37    pub fn code(&self) -> &'static str {
38        match self {
39            AppError::Provider(_) => "provider_error",
40            AppError::Tool(_) => "tool_error",
41            AppError::PermissionDenied(_) => "permission_denied",
42            AppError::BadRequest(_) => "bad_request",
43            AppError::MaxTurnsExceeded(_) => "max_turns_exceeded",
44            AppError::Interrupted => "interrupted",
45            AppError::Unauthorized => "unauthorized",
46            AppError::Internal(_) => "internal_error",
47        }
48    }
49
50    pub fn http_status(&self) -> StatusCode {
51        match self {
52            AppError::BadRequest(_) => StatusCode::BAD_REQUEST,
53            AppError::PermissionDenied(_) => StatusCode::FORBIDDEN,
54            AppError::MaxTurnsExceeded(_) => StatusCode::UNPROCESSABLE_ENTITY,
55            AppError::Interrupted => StatusCode::OK,
56            AppError::Unauthorized => StatusCode::UNAUTHORIZED,
57            AppError::Provider(_) | AppError::Tool(_) | AppError::Internal(_) => {
58                StatusCode::INTERNAL_SERVER_ERROR
59            }
60        }
61    }
62}
63
64impl IntoResponse for AppError {
65    fn into_response(self) -> Response {
66        let code = self.code();
67        let status = self.http_status();
68        let public_message = match &self {
69            AppError::Provider(_) | AppError::Tool(_) | AppError::Internal(_) => {
70                tracing::error!(error.code = %code, error = %self, "internal error");
71                "Internal server error".to_string()
72            }
73            _ => self.to_string(),
74        };
75        let body = axum::Json(json!({
76            "error": public_message,
77            "code": code,
78        }));
79        (status, body).into_response()
80    }
81}
82
83impl From<serde_json::Error> for AppError {
84    fn from(e: serde_json::Error) -> Self {
85        AppError::BadRequest(e.to_string())
86    }
87}
88
89pub type AppResult<T> = Result<T, AppError>;