Skip to main content

quicknode_sdk/
errors.rs

1#[derive(Debug, thiserror::Error)]
2pub enum SdkError {
3    #[error("HTTP error: {0}")]
4    Http(#[from] reqwest::Error),
5
6    #[error("API error (status {status}): {body}")]
7    Api {
8        status: reqwest::StatusCode,
9        body: String,
10    },
11
12    #[error("Failed to decode response: {source}\nBody: {body}")]
13    Decode {
14        #[source]
15        source: serde_json::Error,
16        body: String,
17    },
18
19    #[error("Invalid URL: {0}")]
20    UrlParse(#[from] url::ParseError),
21
22    #[error("Configuration error: {0}")]
23    Config(String),
24
25    #[error("JSON-RPC error (code {code}): {message}")]
26    Rpc { code: i64, message: String },
27}
28
29// Classifies a transport-level HTTP failure. Bindings use this to pick a
30// typed exception subclass (TimeoutError / ConnectionError / HttpError) so the
31// reqwest predicate logic lives in one place.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum HttpKind {
34    Timeout,
35    Connect,
36    Other,
37}
38
39impl SdkError {
40    pub fn http_kind(&self) -> Option<HttpKind> {
41        match self {
42            SdkError::Http(e) if e.is_timeout() => Some(HttpKind::Timeout),
43            SdkError::Http(e) if e.is_connect() => Some(HttpKind::Connect),
44            SdkError::Http(_) => Some(HttpKind::Other),
45            _ => None,
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn api_error_display_includes_status_and_body() {
56        let err = SdkError::Api {
57            status: reqwest::StatusCode::NOT_FOUND,
58            body: "not found".to_string(),
59        };
60        let s = err.to_string();
61        assert!(s.contains("404"), "expected 404 in {s}");
62        assert!(s.contains("not found"), "expected body in {s}");
63    }
64
65    #[test]
66    fn config_error_display() {
67        let err = SdkError::Config("missing api key".to_string());
68        assert!(err.to_string().contains("missing api key"));
69    }
70
71    #[test]
72    #[allow(clippy::unwrap_used)]
73    fn http_kind_none_for_non_http_variants() {
74        assert!(SdkError::Config("x".to_string()).http_kind().is_none());
75        let decode_err = SdkError::Decode {
76            source: serde_json::from_str::<i32>("bad").unwrap_err(),
77            body: "bad".to_string(),
78        };
79        assert!(decode_err.http_kind().is_none());
80    }
81}