xmtp_proto/traits/
error.rs1use std::fmt::Display;
2
3use crate::{ApiEndpoint, ProtoError};
4use thiserror::Error;
5use xmtp_common::{BoxDynError, ErrorCode, RetryableError, retryable};
6
7#[derive(Clone, Copy, Debug, Error, ErrorCode)]
9pub enum AuthError {
10 #[error("credential rejected")]
12 CredentialRejected { retryable: bool },
13 #[error("auth callback failed")]
15 CallbackFailed { retryable: bool },
16 #[error("auth attempts exhausted")]
18 Exhausted,
19 #[error("auth credential missing")]
21 MissingCredential,
22}
23
24impl RetryableError for AuthError {
25 fn is_retryable(&self) -> bool {
26 match self {
27 Self::CredentialRejected { retryable } | Self::CallbackFailed { retryable } => {
28 *retryable
29 }
30 Self::Exhausted | Self::MissingCredential => false,
31 }
32 }
33}
34
35impl AuthError {
36 pub fn is_locked_out(&self) -> bool {
41 matches!(self, Self::Exhausted)
42 }
43}
44
45impl ApiClientError {
46 pub fn is_locked_out(&self) -> bool {
50 matches!(self, Self::Auth(auth) if auth.is_locked_out())
51 }
52}
53
54#[derive(Debug, Error, ErrorCode)]
55#[non_exhaustive]
56pub enum ApiClientError {
57 #[error(transparent)]
58 #[error_code(inherit)]
59 Auth(#[from] AuthError),
60 #[error("api client at endpoint \"{}\" has error {}", endpoint, source)]
62 ClientWithEndpoint {
63 endpoint: String,
64 source: NetworkError,
66 },
67 #[error("client errored {}", source)]
69 Client { source: NetworkError },
70 #[error(transparent)]
72 Http(#[from] http::Error),
73 #[error(transparent)]
75 Body(#[from] BodyError),
76 #[error(transparent)]
78 DecodeError(#[from] prost::DecodeError),
79 #[error(transparent)]
81 Conversion(#[from] crate::ConversionError),
82 #[error(transparent)]
84 ProtoError(#[from] ProtoError),
85 #[error(transparent)]
87 InvalidUri(#[from] http::uri::InvalidUri),
88 #[error(transparent)]
90 Expired(#[from] xmtp_common::time::Expired),
91 #[error("{0}")]
93 Other(Box<dyn RetryableError>),
94 #[error("{0}")]
96 OtherUnretryable(BoxDynError),
97 #[error("Writes are disabled on this client.")]
99 WritesDisabled,
100}
101
102#[derive(Debug)]
106pub struct NetworkError {
107 source: Box<dyn RetryableError>,
108}
109
110impl std::error::Error for NetworkError {
111 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
112 Some(self.source.as_ref())
113 }
114}
115
116impl Display for NetworkError {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 write!(f, "{}", self.source)
119 }
120}
121
122impl RetryableError for NetworkError {
123 fn is_retryable(&self) -> bool {
124 self.source.is_retryable()
125 }
126}
127
128impl NetworkError {
129 pub fn new(e: impl RetryableError + 'static) -> Self {
130 NetworkError {
131 source: Box::new(e),
132 }
133 }
134}
135
136impl ApiClientError {
137 pub fn new(endpoint: ApiEndpoint, source: impl RetryableError + 'static) -> Self {
138 Self::ClientWithEndpoint {
139 endpoint: endpoint.to_string(),
140 source: NetworkError::new(source),
141 }
142 }
143
144 pub fn endpoint(self, endpoint: impl ToString) -> Self {
146 match self {
147 Self::Client { source } => Self::ClientWithEndpoint {
148 source,
149 endpoint: endpoint.to_string(),
150 },
151 v => v,
152 }
153 }
154
155 pub fn client(client: impl RetryableError + 'static) -> Self {
156 Self::Client {
157 source: NetworkError::new(client),
158 }
159 }
160
161 pub fn network_error(&self) -> Option<&NetworkError> {
164 use ApiClientError::*;
165 match self {
166 ClientWithEndpoint { source, .. } | Client { source, .. } => Some(source),
167 _ => None,
168 }
169 }
170}
171
172impl ApiClientError {
173 pub fn other<R: RetryableError + 'static>(e: R) -> Self {
174 ApiClientError::Other(Box::new(e))
175 }
176}
177
178impl RetryableError for ApiClientError {
179 fn is_retryable(&self) -> bool {
180 use ApiClientError::*;
181 match self {
182 Client { source } => retryable!(*source),
183 ClientWithEndpoint { source, .. } => retryable!(source),
184 Auth(e) => retryable!(e),
185 Body(e) => retryable!(e),
186 Http(_) => false,
187 DecodeError(_) => false,
188 Conversion(_) => false,
189 ProtoError(_) => false,
190 InvalidUri(_) => false,
191 Expired(_) => true,
192 Other(r) => retryable!(r),
193 OtherUnretryable(_) => false,
194 WritesDisabled => false,
195 }
196 }
197}
198
199impl From<std::convert::Infallible> for ApiClientError {
201 fn from(_v: std::convert::Infallible) -> ApiClientError {
202 unreachable!("Infallible errors can never occur")
203 }
204}
205
206#[derive(Debug, Error)]
207pub enum BodyError {
208 #[error(transparent)]
209 UninitializedField(#[from] derive_builder::UninitializedFieldError),
210 #[error(transparent)]
211 Conversion(#[from] crate::ConversionError),
212}
213
214impl RetryableError for BodyError {
215 fn is_retryable(&self) -> bool {
216 false
217 }
218}
219
220pub fn grpc_status<'a>(
222 mut error: &'a (dyn std::error::Error + 'static),
223) -> Option<&'a tonic::Status> {
224 loop {
225 if let Some(status) = error.downcast_ref::<tonic::Status>() {
226 return Some(status);
227 }
228 error = match error.downcast_ref::<ApiClientError>() {
229 Some(ApiClientError::Other(inner)) => inner.as_ref(),
230 Some(ApiClientError::OtherUnretryable(inner)) => inner.as_ref(),
231 _ => error.source()?,
232 };
233 }
234}