Skip to main content

everruns_sdk/
error.rs

1//! Error types for Everruns SDK
2
3use thiserror::Error;
4
5/// Errors that can occur when using the Everruns SDK
6#[derive(Error, Debug)]
7pub enum Error {
8    /// API returned an error response
9    #[error("API error: {code} - {message}")]
10    Api {
11        code: String,
12        message: String,
13        status: u16,
14    },
15
16    /// Network or HTTP error
17    #[error("Network error: {0}")]
18    Network(#[from] reqwest::Error),
19
20    /// Authentication error
21    #[error("Authentication error: {0}")]
22    Auth(String),
23
24    /// Environment variable not found
25    #[error("Environment variable not found: {0}")]
26    EnvVar(String),
27
28    /// JSON serialization/deserialization error
29    #[error("JSON error: {0}")]
30    Json(#[from] serde_json::Error),
31
32    /// URL parsing error
33    #[error("URL error: {0}")]
34    Url(#[from] url::ParseError),
35
36    /// SSE stream error
37    #[error("SSE error: {0}")]
38    Sse(String),
39
40    /// Server-initiated graceful disconnect with retry hint
41    #[error("Graceful disconnect: reason={reason}, retry_ms={retry_ms}")]
42    GracefulDisconnect { reason: String, retry_ms: u64 },
43}
44
45/// API error response from the server
46#[derive(Debug, serde::Serialize, serde::Deserialize)]
47#[non_exhaustive]
48pub struct ApiErrorResponse {
49    pub error: ApiErrorDetail,
50}
51
52/// Detail of an API error
53#[derive(Debug, serde::Serialize, serde::Deserialize)]
54#[non_exhaustive]
55pub struct ApiErrorDetail {
56    pub code: String,
57    pub message: String,
58}
59
60impl Error {
61    pub(crate) fn from_api_response(status: u16, body: &str) -> Self {
62        if let Ok(err) = serde_json::from_str::<ApiErrorResponse>(body) {
63            Error::Api {
64                code: err.error.code,
65                message: err.error.message,
66                status,
67            }
68        } else {
69            // Simplify HTML responses to avoid verbose error messages
70            let message = if is_html_response(body) {
71                format!("HTTP {status}")
72            } else {
73                body.to_string()
74            };
75            Error::Api {
76                code: "unknown".to_string(),
77                message,
78                status,
79            }
80        }
81    }
82}
83
84/// Check if the body looks like an HTML response
85fn is_html_response(body: &str) -> bool {
86    let trimmed = body.trim_start();
87    trimmed.starts_with("<!DOCTYPE") || trimmed.starts_with("<html") || trimmed.starts_with("<HTML")
88}
89
90/// Result type for Everruns SDK operations
91pub type Result<T> = std::result::Result<T, Error>;