Skip to main content

execgo_runtime/
error.rs

1use axum::{
2    http::StatusCode,
3    response::{IntoResponse, Response},
4    Json,
5};
6use serde::Serialize;
7use serde_json::json;
8use thiserror::Error;
9
10use crate::types::{ErrorCode, RuntimeErrorInfo};
11
12pub type AppResult<T> = Result<T, AppError>;
13
14#[derive(Debug, Error)]
15pub enum AppError {
16    #[error("invalid input: {0}")]
17    InvalidInput(String),
18    #[error("task not found: {0}")]
19    NotFound(String),
20    #[error("queue is full")]
21    QueueFull,
22    #[error("task is already terminal: {0}")]
23    Conflict(String),
24    #[error("launch failed: {0}")]
25    LaunchFailed(String),
26    #[error("sandbox setup failed: {0}")]
27    SandboxSetup(String),
28    #[error("unsupported capability: {0}")]
29    UnsupportedCapability(String),
30    #[error("insufficient resources: {0}")]
31    InsufficientResources(String),
32    #[error("io error: {0}")]
33    Io(#[from] std::io::Error),
34    #[error("sqlite error: {0}")]
35    Sqlite(#[from] rusqlite::Error),
36    #[error("json error: {0}")]
37    Json(#[from] serde_json::Error),
38    #[error("http error: {0}")]
39    Http(#[from] reqwest::Error),
40    #[error("internal error: {0}")]
41    Internal(String),
42}
43
44impl AppError {
45    pub fn code(&self) -> ErrorCode {
46        match self {
47            AppError::InvalidInput(_) => ErrorCode::InvalidInput,
48            AppError::NotFound(_) => ErrorCode::Internal,
49            AppError::QueueFull => ErrorCode::ResourceLimitExceeded,
50            AppError::Conflict(_) => ErrorCode::Internal,
51            AppError::LaunchFailed(_) => ErrorCode::LaunchFailed,
52            AppError::SandboxSetup(_) => ErrorCode::SandboxSetupFailed,
53            AppError::UnsupportedCapability(_) => ErrorCode::UnsupportedCapability,
54            AppError::InsufficientResources(_) => ErrorCode::InsufficientResources,
55            AppError::Io(_) | AppError::Sqlite(_) | AppError::Json(_) | AppError::Http(_) => {
56                ErrorCode::Internal
57            }
58            AppError::Internal(_) => ErrorCode::Internal,
59        }
60    }
61
62    pub fn status_code(&self) -> StatusCode {
63        match self {
64            AppError::InvalidInput(_) => StatusCode::BAD_REQUEST,
65            AppError::NotFound(_) => StatusCode::NOT_FOUND,
66            AppError::QueueFull => StatusCode::TOO_MANY_REQUESTS,
67            AppError::Conflict(_) => StatusCode::CONFLICT,
68            AppError::LaunchFailed(_)
69            | AppError::SandboxSetup(_)
70            | AppError::UnsupportedCapability(_)
71            | AppError::InsufficientResources(_) => StatusCode::BAD_REQUEST,
72            AppError::Io(_)
73            | AppError::Sqlite(_)
74            | AppError::Json(_)
75            | AppError::Http(_)
76            | AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
77        }
78    }
79
80    pub fn as_runtime_error(&self) -> RuntimeErrorInfo {
81        RuntimeErrorInfo {
82            code: self.code(),
83            message: self.to_string(),
84            details: None,
85        }
86    }
87}
88
89#[derive(Debug, Serialize)]
90struct ErrorEnvelope {
91    error: RuntimeErrorInfo,
92}
93
94impl IntoResponse for AppError {
95    fn into_response(self) -> Response {
96        let status = self.status_code();
97        let body = Json(ErrorEnvelope {
98            error: self.as_runtime_error(),
99        });
100        (status, body).into_response()
101    }
102}
103
104pub fn json_error(code: ErrorCode, message: impl Into<String>) -> serde_json::Value {
105    json!({
106        "error": {
107            "code": code,
108            "message": message.into(),
109        }
110    })
111}