Skip to main content

samvadsetu/
error.rs

1// error.rs — unified error type for all providers
2
3use std::fmt;
4
5/// All errors that can arise when calling an LLM API.
6#[derive(Debug, Clone)]
7pub enum SamvadSetuError {
8    /// The server returned a non-success HTTP status.
9    Http { status: u16, body: String },
10
11    /// The provider returned a structured error in the response body.
12    Provider {
13        /// Provider-specific error type string (e.g. "invalid_request_error").
14        error_type: String,
15        /// Human-readable error message.
16        message: String,
17        /// The parameter that caused the error, if reported.
18        param: Option<String>,
19        /// Provider-specific error code, if reported.
20        code: Option<String>,
21    },
22
23    /// The API rate limit was exceeded.
24    RateLimit {
25        /// Seconds to wait before retrying, if the server provided it.
26        retry_after_secs: Option<u64>,
27        message: String,
28    },
29
30    /// Failed to serialize the request or deserialize the response.
31    Parse {
32        message: String,
33        /// The raw response text that failed to parse, when available.
34        raw_response: Option<String>,
35    },
36
37    /// A required field was missing or invalid in the library configuration.
38    Config(String),
39
40    /// Authentication failed (missing or invalid API key).
41    Auth(String),
42
43    /// The request timed out before a response was received.
44    Timeout,
45
46    /// A lower-level network error occurred.
47    Network(String),
48
49    /// A batch job was queried but has not finished yet.
50    BatchNotComplete { batch_id: String, status: String },
51
52    /// The requested feature is not supported by this provider.
53    UnsupportedFeature { provider: String, feature: String },
54}
55
56impl fmt::Display for SamvadSetuError {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::Http { status, body } => write!(f, "HTTP {status}: {body}"),
60            Self::Provider { error_type, message, code, param } => {
61                write!(f, "Provider error [{error_type}]")?;
62                if let Some(c) = code {
63                    write!(f, " (code: {c})")?;
64                }
65                if let Some(p) = param {
66                    write!(f, " (param: {p})")?;
67                }
68                write!(f, ": {message}")
69            }
70            Self::RateLimit { retry_after_secs, message } => match retry_after_secs {
71                Some(s) => write!(f, "Rate limited (retry after {s}s): {message}"),
72                None => write!(f, "Rate limited: {message}"),
73            },
74            Self::Parse { message, .. } => write!(f, "Parse error: {message}"),
75            Self::Config(msg) => write!(f, "Config error: {msg}"),
76            Self::Auth(msg) => write!(f, "Auth error: {msg}"),
77            Self::Timeout => write!(f, "Request timed out"),
78            Self::Network(msg) => write!(f, "Network error: {msg}"),
79            Self::BatchNotComplete { batch_id, status } => {
80                write!(f, "Batch {batch_id} not complete (status: {status})")
81            }
82            Self::UnsupportedFeature { provider, feature } => {
83                write!(f, "{provider} does not support {feature}")
84            }
85        }
86    }
87}
88
89impl std::error::Error for SamvadSetuError {}
90
91/// Parse a reqwest error into a `SamvadSetuError`.
92pub(crate) fn from_reqwest_error(e: reqwest::Error) -> SamvadSetuError {
93    if e.is_timeout() {
94        SamvadSetuError::Timeout
95    } else if e.is_connect() {
96        SamvadSetuError::Network(format!("Connection failed: {e}"))
97    } else {
98        SamvadSetuError::Network(e.to_string())
99    }
100}
101
102/// Try to extract a structured `Provider` error from a JSON error body returned by
103/// OpenAI-compatible APIs ({"error": {"type", "message", "param", "code"}}).
104pub(crate) fn parse_openai_error_body(
105    status: u16,
106    body: &str,
107) -> SamvadSetuError {
108    if let Ok(val) = serde_json::from_str::<serde_json::Value>(body)
109        && let Some(err) = val.get("error")
110    {
111        return SamvadSetuError::Provider {
112            error_type: err
113                .get("type")
114                .and_then(|v| v.as_str())
115                .unwrap_or("unknown")
116                .to_string(),
117            message: err
118                .get("message")
119                .and_then(|v| v.as_str())
120                .unwrap_or(body)
121                .to_string(),
122            param: err.get("param").and_then(|v| v.as_str()).map(str::to_string),
123            code: err.get("code").and_then(|v| v.as_str()).map(str::to_string),
124        };
125    }
126    SamvadSetuError::Http { status, body: body.to_string() }
127}
128
129/// Try to extract a structured error from an Anthropic error body
130/// ({"type":"error","error":{"type","message"}}).
131pub(crate) fn parse_anthropic_error_body(status: u16, body: &str) -> SamvadSetuError {
132    if let Ok(val) = serde_json::from_str::<serde_json::Value>(body)
133        && let Some(err) = val.get("error")
134    {
135        return SamvadSetuError::Provider {
136            error_type: err
137                .get("type")
138                .and_then(|v| v.as_str())
139                .unwrap_or("api_error")
140                .to_string(),
141            message: err
142                .get("message")
143                .and_then(|v| v.as_str())
144                .unwrap_or(body)
145                .to_string(),
146            param: None,
147            code: None,
148        };
149    }
150    SamvadSetuError::Http { status, body: body.to_string() }
151}