Skip to main content

xmtp_proto/traits/
error.rs

1use std::fmt::Display;
2
3use crate::{ApiEndpoint, ProtoError};
4use thiserror::Error;
5use xmtp_common::{BoxDynError, RetryableError, retryable};
6
7#[derive(Debug, Error)]
8#[non_exhaustive]
9pub enum ApiClientError {
10    /// The client encountered an error.
11    #[error("api client at endpoint \"{}\" has error {}", endpoint, source)]
12    ClientWithEndpoint {
13        endpoint: String,
14        /// The client error.
15        source: NetworkError,
16    },
17    #[error("client errored {}", source)]
18    Client { source: NetworkError },
19    #[error(transparent)]
20    Http(#[from] http::Error),
21    #[error(transparent)]
22    Body(#[from] BodyError),
23    #[error(transparent)]
24    DecodeError(#[from] prost::DecodeError),
25    #[error(transparent)]
26    Conversion(#[from] crate::ConversionError),
27    #[error(transparent)]
28    ProtoError(#[from] ProtoError),
29    #[error(transparent)]
30    InvalidUri(#[from] http::uri::InvalidUri),
31    #[error(transparent)]
32    Expired(#[from] xmtp_common::time::Expired),
33    #[error("{0}")]
34    Other(Box<dyn RetryableError>),
35    #[error("{0}")]
36    OtherUnretryable(BoxDynError),
37    #[error("Writes are disabled on this client.")]
38    WritesDisabled,
39}
40
41/// A lower level NetworkError, like gRPC/QUIC/HTTP/1.1 errors go here.
42/// use [`ApiClientError::new`] to construct
43// needed because of AsDynError sealed trait
44#[derive(Debug)]
45pub struct NetworkError {
46    source: Box<dyn RetryableError>,
47}
48
49impl std::error::Error for NetworkError {
50    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51        Some(self.source.as_ref())
52    }
53}
54
55impl Display for NetworkError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        write!(f, "{}", self.source)
58    }
59}
60
61impl RetryableError for NetworkError {
62    fn is_retryable(&self) -> bool {
63        self.source.is_retryable()
64    }
65}
66
67impl NetworkError {
68    pub fn new(e: impl RetryableError + 'static) -> Self {
69        NetworkError {
70            source: Box::new(e),
71        }
72    }
73}
74
75impl ApiClientError {
76    pub fn new(endpoint: ApiEndpoint, source: impl RetryableError + 'static) -> Self {
77        Self::ClientWithEndpoint {
78            endpoint: endpoint.to_string(),
79            source: NetworkError::new(source),
80        }
81    }
82
83    /// add an endpoint to a ApiError::Client error
84    pub fn endpoint(self, endpoint: impl ToString) -> Self {
85        match self {
86            Self::Client { source } => Self::ClientWithEndpoint {
87                source,
88                endpoint: endpoint.to_string(),
89            },
90            v => v,
91        }
92    }
93
94    pub fn client(client: impl RetryableError + 'static) -> Self {
95        Self::Client {
96            source: NetworkError::new(client),
97        }
98    }
99
100    /// Try to pull a [`NetworkError`] out of this error enum.
101    /// returns None if there's no match
102    pub fn network_error(&self) -> Option<&NetworkError> {
103        use ApiClientError::*;
104        match self {
105            ClientWithEndpoint { source, .. } | Client { source, .. } => Some(source),
106            _ => None,
107        }
108    }
109}
110
111impl ApiClientError {
112    pub fn other<R: RetryableError + 'static>(e: R) -> Self {
113        ApiClientError::Other(Box::new(e))
114    }
115}
116
117impl RetryableError for ApiClientError {
118    fn is_retryable(&self) -> bool {
119        use ApiClientError::*;
120        match self {
121            Client { source } => retryable!(*source),
122            ClientWithEndpoint { source, .. } => retryable!(source),
123            Body(e) => retryable!(e),
124            Http(_) => true,
125            DecodeError(_) => false,
126            Conversion(_) => false,
127            ProtoError(_) => false,
128            InvalidUri(_) => false,
129            Expired(_) => true,
130            Other(r) => retryable!(r),
131            OtherUnretryable(_) => false,
132            WritesDisabled => false,
133        }
134    }
135}
136
137// Infallible errors by definition can never occur
138impl From<std::convert::Infallible> for ApiClientError {
139    fn from(_v: std::convert::Infallible) -> ApiClientError {
140        unreachable!("Infallible errors can never occur")
141    }
142}
143
144#[derive(Debug, Error)]
145pub enum BodyError {
146    #[error(transparent)]
147    UninitializedField(#[from] derive_builder::UninitializedFieldError),
148    #[error(transparent)]
149    Conversion(#[from] crate::ConversionError),
150}
151
152impl RetryableError for BodyError {
153    fn is_retryable(&self) -> bool {
154        false
155    }
156}