Skip to main content

context69_sdk/
error.rs

1use std::{error::Error as StdError, fmt, time::Duration};
2
3use reqwest::StatusCode;
4
5#[derive(Debug)]
6pub enum Error {
7    InvalidBaseUrl(String),
8    InvalidHeader(String),
9    InvalidPersonalAccessToken(String),
10    InvalidResponse(String),
11    Http(reqwest::Error),
12    Serialization(serde_json::Error),
13    HttpStatus {
14        status: StatusCode,
15        api_error: Option<String>,
16        body: String,
17    },
18    AuthenticationRequired,
19    UrlJoin {
20        path: String,
21        source: url::ParseError,
22    },
23    InvalidTimeout(Duration),
24    TaskWaitTimeout {
25        task_id: uuid::Uuid,
26        timeout: Duration,
27    },
28}
29
30impl fmt::Display for Error {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Self::InvalidBaseUrl(url) => write!(f, "invalid base url: {url}"),
34            Self::InvalidHeader(value) => write!(f, "invalid header value: {value}"),
35            Self::InvalidPersonalAccessToken(message) => write!(f, "{message}"),
36            Self::InvalidResponse(message) => write!(f, "{message}"),
37            Self::Http(error) => write!(f, "{error}"),
38            Self::Serialization(error) => write!(f, "{error}"),
39            Self::HttpStatus {
40                status,
41                api_error,
42                body,
43            } => {
44                if let Some(error) = api_error {
45                    write!(f, "http {status}: {error}")
46                } else {
47                    write!(f, "http {status}: {body}")
48                }
49            }
50            Self::AuthenticationRequired => {
51                write!(
52                    f,
53                    "personal access token is required before calling this API"
54                )
55            }
56            Self::UrlJoin { path, source } => {
57                write!(f, "failed to resolve path '{path}': {source}")
58            }
59            Self::InvalidTimeout(timeout) => {
60                write!(f, "invalid timeout: {:?}", timeout)
61            }
62            Self::TaskWaitTimeout { task_id, timeout } => {
63                write!(f, "timed out waiting for task {task_id} after {timeout:?}")
64            }
65        }
66    }
67}
68
69impl StdError for Error {
70    fn source(&self) -> Option<&(dyn StdError + 'static)> {
71        match self {
72            Self::Http(error) => Some(error),
73            Self::Serialization(error) => Some(error),
74            Self::UrlJoin { source, .. } => Some(source),
75            _ => None,
76        }
77    }
78}
79
80impl From<reqwest::Error> for Error {
81    fn from(value: reqwest::Error) -> Self {
82        Self::Http(value)
83    }
84}
85
86impl From<serde_json::Error> for Error {
87    fn from(value: serde_json::Error) -> Self {
88        Self::Serialization(value)
89    }
90}
91
92impl Error {
93    pub fn is_retryable(&self) -> bool {
94        match self {
95            Self::Http(_) => true,
96            Self::HttpStatus { status, .. } => {
97                matches!(status.as_u16(), 408 | 425 | 429 | 500..=599)
98            }
99            _ => false,
100        }
101    }
102}