Skip to main content

crabllm_core/
error.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// Shared error type for the crabllm workspace.
5#[derive(Debug)]
6pub enum Error {
7    /// TOML config parse error or missing env var.
8    Config(String),
9    /// Upstream provider returned an error status.
10    Provider { status: u16, body: String },
11    /// JSON serialization/deserialization error.
12    Json(serde_json::Error),
13    /// Catch-all for internal errors.
14    Internal(String),
15    /// Request to upstream provider timed out.
16    Timeout,
17}
18
19impl fmt::Display for Error {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Error::Config(msg) => write!(f, "config error: {msg}"),
23            Error::Provider { status, body } => {
24                write!(f, "provider error (HTTP {status}): {body}")
25            }
26            Error::Json(e) => write!(f, "json error: {e}"),
27            Error::Internal(msg) => write!(f, "internal error: {msg}"),
28            Error::Timeout => write!(f, "request timed out"),
29        }
30    }
31}
32
33impl Error {
34    /// Whether this error is transient and the request should be retried.
35    /// Transient: 429 (rate limit), 500, 502, 503, 504 (server errors),
36    /// and connection/internal errors.
37    pub fn is_transient(&self) -> bool {
38        match self {
39            Error::Provider { status, .. } => matches!(status, 429 | 500 | 502 | 503 | 504),
40            Error::Internal(_) | Error::Timeout => true,
41            _ => false,
42        }
43    }
44}
45
46impl std::error::Error for Error {
47    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48        match self {
49            Error::Json(e) => Some(e),
50            _ => None,
51        }
52    }
53}
54
55#[cfg(feature = "gateway")]
56impl From<toml::de::Error> for Error {
57    fn from(e: toml::de::Error) -> Self {
58        Error::Config(e.to_string())
59    }
60}
61
62impl From<serde_json::Error> for Error {
63    fn from(e: serde_json::Error) -> Self {
64        Error::Json(e)
65    }
66}
67
68/// OpenAI-compatible error response returned to clients.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ApiError {
71    pub error: ApiErrorBody,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ApiErrorBody {
76    pub message: String,
77    #[serde(rename = "type")]
78    pub kind: String,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub param: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub code: Option<String>,
83}
84
85impl ApiError {
86    pub fn new(message: impl Into<String>, kind: impl Into<String>) -> Self {
87        ApiError {
88            error: ApiErrorBody {
89                message: message.into(),
90                kind: kind.into(),
91                param: None,
92                code: None,
93            },
94        }
95    }
96}