Skip to main content

zerokms_protocol/
error.rs

1use static_assertions::assert_impl_all;
2use std::error::Error as StdError;
3use thiserror::Error;
4
5pub trait RetryableError {
6    fn can_retry(&self) -> bool;
7}
8
9type ShareableError = Box<dyn StdError + Send + Sync>;
10
11#[derive(Debug)]
12pub enum ViturRequestErrorKind {
13    PrepareRequest,
14    SendRequest,
15    NotFound,
16    Conflict,
17    FailureResponse,
18    ParseResponse,
19    Unauthorized,
20    Forbidden,
21    Other,
22}
23
24impl std::fmt::Display for ViturRequestErrorKind {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(
27            f,
28            "{}",
29            match self {
30                Self::PrepareRequest => "PrepareRequest",
31                Self::SendRequest => "SendRequest",
32                Self::NotFound => "NotFound",
33                Self::Conflict => "Conflict",
34                Self::FailureResponse => "FailureResponse",
35                Self::ParseResponse => "ParseResponse",
36                Self::Unauthorized => "Unauthorized",
37                Self::Forbidden => "Forbidden",
38                Self::Other => "Other",
39            }
40        )
41    }
42}
43
44#[derive(Debug, Error)]
45#[error("{kind}: {message}")]
46pub struct ViturRequestError {
47    pub kind: ViturRequestErrorKind,
48    pub message: &'static str,
49    // `#[source]` exposes the wrapped error through `Error::source()` so
50    // anyhow chain printing (`{:?}` / `{:#}`) and `tracing` can walk into
51    // it — without it, the chain stops at this struct and the underlying
52    // transport / parse / response error (e.g. a reqwest error wrapping
53    // hyper / TCP) is hidden. Display intentionally omits `{error}` to
54    // avoid duplicating it in chain output (anyhow already walks `.source()`).
55    #[source]
56    pub error: ShareableError,
57    can_retry: bool,
58}
59
60// ViturRequestError should be able to be sent and shared between threads to be able to support
61// async as well as `anyhow!` for async.
62assert_impl_all!(ViturRequestError: Send, Sync);
63
64impl ViturRequestError {
65    #[inline]
66    pub fn new(
67        kind: ViturRequestErrorKind,
68        message: &'static str,
69        error: impl StdError + 'static + Send + Sync,
70    ) -> Self {
71        Self {
72            kind,
73            message,
74            error: Box::new(error),
75            can_retry: false,
76        }
77    }
78
79    #[inline]
80    pub fn prepare(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
81        Self {
82            kind: ViturRequestErrorKind::PrepareRequest,
83            message,
84            error: Box::new(error),
85            can_retry: false,
86        }
87    }
88
89    #[inline]
90    pub fn parse(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
91        Self {
92            kind: ViturRequestErrorKind::ParseResponse,
93            message,
94            error: Box::new(error),
95            can_retry: false,
96        }
97    }
98
99    #[inline]
100    pub fn send(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
101        Self {
102            kind: ViturRequestErrorKind::SendRequest,
103            message,
104            error: Box::new(error),
105            can_retry: false,
106        }
107    }
108
109    #[inline]
110    pub fn not_found(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
111        Self {
112            kind: ViturRequestErrorKind::NotFound,
113            message,
114            error: Box::new(error),
115            can_retry: false,
116        }
117    }
118
119    #[inline]
120    pub fn conflict(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
121        Self {
122            kind: ViturRequestErrorKind::Conflict,
123            message,
124            error: Box::new(error),
125            can_retry: false,
126        }
127    }
128
129    #[inline]
130    pub fn response(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
131        Self {
132            kind: ViturRequestErrorKind::FailureResponse,
133            message,
134            error: Box::new(error),
135            can_retry: false,
136        }
137    }
138
139    #[inline]
140    pub fn forbidden(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
141        Self {
142            kind: ViturRequestErrorKind::Forbidden,
143            message,
144            error: Box::new(error),
145            can_retry: false,
146        }
147    }
148
149    #[inline]
150    pub fn other(message: &'static str, error: impl StdError + 'static + Send + Sync) -> Self {
151        Self {
152            kind: ViturRequestErrorKind::Other,
153            message,
154            error: Box::new(error),
155            can_retry: false,
156        }
157    }
158
159    pub fn is_conflict(&self) -> bool {
160        matches!(self.kind, ViturRequestErrorKind::Conflict)
161    }
162
163    pub fn with_retryable(mut self, can_retry: bool) -> Self {
164        self.can_retry = can_retry;
165        self
166    }
167
168    pub fn retryable(self) -> Self {
169        self.with_retryable(true)
170    }
171}
172
173impl RetryableError for ViturRequestError {
174    fn can_retry(&self) -> bool {
175        self.can_retry
176    }
177}