Skip to main content

steer_core/api/
error.rs

1use eventsource_stream::EventStreamError;
2use thiserror::Error;
3
4#[derive(Error, Debug, Clone, PartialEq, Eq)]
5pub enum SseParseError {
6    #[error("UTF-8 error: {details}")]
7    Utf8 { details: String },
8    #[error("Parse error: {details}")]
9    Parser { details: String },
10    #[error("Transport error: {details}")]
11    Transport { details: String },
12}
13
14impl<E> From<EventStreamError<E>> for SseParseError
15where
16    E: std::error::Error,
17{
18    fn from(err: EventStreamError<E>) -> Self {
19        match err {
20            EventStreamError::Utf8(err) => Self::Utf8 {
21                details: err.to_string(),
22            },
23            EventStreamError::Parser(err) => Self::Parser {
24                details: err.to_string(),
25            },
26            EventStreamError::Transport(err) => Self::Transport {
27                details: err.to_string(),
28            },
29        }
30    }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ProviderStreamErrorKind {
35    StreamError,
36    StreamRetry,
37    RateLimitExceeded,
38    ResponseFailed,
39    Overloaded,
40    ServiceUnavailable,
41    Timeout,
42    Unknown(String),
43}
44
45impl ProviderStreamErrorKind {
46    pub fn from_provider_error_type(error_type: &str) -> Self {
47        match error_type {
48            "stream_error" | "error" => Self::StreamError,
49            "stream_retry" => Self::StreamRetry,
50            "rate_limit_exceeded" | "rate_limit_error" => Self::RateLimitExceeded,
51            "response_failed" => Self::ResponseFailed,
52            "overloaded_error" => Self::Overloaded,
53            "service_unavailable_error" => Self::ServiceUnavailable,
54            "timeout_error" => Self::Timeout,
55            other => Self::Unknown(other.to_string()),
56        }
57    }
58
59    pub fn is_retryable(&self) -> bool {
60        matches!(
61            self,
62            Self::StreamError
63                | Self::StreamRetry
64                | Self::RateLimitExceeded
65                | Self::ResponseFailed
66                | Self::Overloaded
67                | Self::ServiceUnavailable
68                | Self::Timeout
69        )
70    }
71}
72
73impl std::fmt::Display for ProviderStreamErrorKind {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            Self::StreamError => f.write_str("stream_error"),
77            Self::StreamRetry => f.write_str("stream_retry"),
78            Self::RateLimitExceeded => f.write_str("rate_limit_exceeded"),
79            Self::ResponseFailed => f.write_str("response_failed"),
80            Self::Overloaded => f.write_str("overloaded_error"),
81            Self::ServiceUnavailable => f.write_str("service_unavailable_error"),
82            Self::Timeout => f.write_str("timeout_error"),
83            Self::Unknown(raw) => f.write_str(raw),
84        }
85    }
86}
87
88#[derive(Error, Debug, Clone, PartialEq, Eq)]
89pub enum StreamError {
90    #[error("Request cancelled")]
91    Cancelled,
92
93    #[error("SSE parse error: {0}")]
94    SseParse(SseParseError),
95
96    #[error("{provider} error ({kind}): {message}")]
97    Provider {
98        provider: String,
99        kind: ProviderStreamErrorKind,
100        raw_error_type: Option<String>,
101        message: String,
102    },
103}
104
105#[derive(Error, Debug)]
106pub enum ApiError {
107    #[error("Network error: {0}")]
108    Network(#[from] reqwest::Error),
109
110    #[error("Authentication failed: {details}")]
111    AuthenticationFailed { provider: String, details: String },
112
113    #[error("Auth error: {0}")]
114    AuthError(String),
115
116    #[error("Rate limited by {provider}: {details}")]
117    RateLimited { provider: String, details: String },
118
119    #[error("Invalid request to {provider}: {details}")]
120    InvalidRequest { provider: String, details: String },
121
122    #[error("{provider} server error (Status: {status_code}): {details}")]
123    ServerError {
124        provider: String,
125        status_code: u16,
126        details: String,
127    },
128
129    #[error("Request timed out for {provider}")]
130    Timeout { provider: String },
131
132    #[error("Request cancelled for {provider}")]
133    Cancelled { provider: String },
134
135    #[error("Failed to parse response from {provider}: {details}")]
136    ResponseParsingError { provider: String, details: String },
137
138    #[error("API returned no choices/candidates for {provider}")]
139    NoChoices { provider: String },
140
141    #[error("Request blocked by {provider}: {details}")]
142    RequestBlocked { provider: String, details: String },
143
144    #[error("Unknown API error from {provider}: {details}")]
145    Unknown { provider: String, details: String },
146
147    #[error("Configuration error: {0}")]
148    Configuration(String),
149
150    #[error("{provider} does not support {feature}: {details}")]
151    UnsupportedFeature {
152        provider: String,
153        feature: String,
154        details: String,
155    },
156
157    #[error("Stream error from {provider}: {details}")]
158    StreamError { provider: String, details: String },
159}
160
161impl From<crate::error::Error> for ApiError {
162    fn from(err: crate::error::Error) -> Self {
163        match err {
164            crate::error::Error::Api(api_err) => api_err,
165            crate::error::Error::Configuration(msg) => ApiError::Configuration(msg),
166            other => ApiError::Unknown {
167                provider: "internal".to_string(),
168                details: format!("Unexpected internal error: {other}"),
169            },
170        }
171    }
172}