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