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}