1#![warn(clippy::unwrap_used)]
2
3pub mod attachments;
4pub mod configuration;
5pub mod identity;
6pub mod mls;
7mod notification;
8pub mod preflight;
9pub mod scw_verifier;
10#[cfg(any(test, feature = "test-utils"))]
11pub mod test_utils;
12
13use std::sync::Arc;
14
15use xmtp_common::{ErrorCode, ExponentialBackoff, Retry, RetryableError, retryable};
16pub use xmtp_proto::api_client::XmtpApi;
17
18pub use identity::*;
19pub use mls::*;
20pub mod chunk;
21pub use chunk::PublishUnit;
22
23pub type Result<T> = std::result::Result<T, ApiError>;
24
25pub mod strategies {
26 use super::*;
27 pub fn exponential_cooldown() -> Retry<ExponentialBackoff> {
28 xmtp_common::Retry::builder().build()
29 }
30}
31
32pub fn dyn_err(e: impl RetryableError + 'static) -> ApiError {
40 fn find(error: &(dyn std::any::Any + 'static)) -> Option<xmtp_proto::api::AuthError> {
41 if let Some(ApiError::Auth(auth)) = error.downcast_ref() {
42 return Some(*auth);
43 }
44 if let Some(boxed) = error.downcast_ref::<Box<ApiError>>() {
45 return find(&**boxed);
46 }
47 if let Some(xmtp_proto::api::ApiClientError::Auth(auth)) = error.downcast_ref() {
48 return Some(*auth);
49 }
50 if let Some(auth) = error.downcast_ref::<xmtp_proto::api::AuthError>() {
51 return Some(*auth);
52 }
53 if let Some(boxed) = error.downcast_ref::<Box<xmtp_proto::api::ApiClientError>>() {
57 return find(&**boxed);
58 }
59 if let Some(boxed) = error.downcast_ref::<Box<xmtp_proto::api::AuthError>>() {
60 return Some(**boxed);
61 }
62 None
63 }
64 if let Some(error) = preflight::failure(&e) {
65 return ApiError::Preflight(error.clone());
66 }
67 if let Some(auth) = find(&e) {
68 return ApiError::Auth(auth);
69 }
70 ApiError::Api(xmtp_proto::api::NetworkError::new(e))
71}
72
73#[derive(Debug, thiserror::Error, ErrorCode)]
74pub enum ApiError {
75 #[error("request preflight failed: {0}")]
77 Preflight(#[source] preflight::PreflightError),
78 #[error(transparent)]
79 #[error_code(inherit)]
80 Auth(#[from] xmtp_proto::api::AuthError),
81 #[error("api client error {0}")]
85 Api(#[source] xmtp_proto::api::NetworkError),
86 #[error("identity history changed")]
88 IdentityUpdateConflict,
89 #[error("envelope exceeds the byte limit")]
91 EnvelopeTooLarge,
92 #[error("atomic publish unit exceeds a request limit")]
94 UnitTooLarge,
95 #[error("one envelope response exceeds a backend limit")]
97 ResponseTooLarge,
98 #[error("invalid backend request: {0}")]
100 InvalidRequest(&'static str),
101 #[error("invalid backend response: {0}")]
103 InvalidResponse(&'static str),
104 #[error(transparent)]
106 InvalidEnvelope(#[from] xmtp_mls_validation::ValidationError),
107 #[error(transparent)]
109 Envelope(#[from] xmtp_api_backend::envelope::EnvelopeError),
110 #[error(transparent)]
114 ProtoConversion(#[from] xmtp_proto::ConversionError),
115 #[error(transparent)]
117 #[error_code(inherit)]
118 HandleRejected(#[from] HandleRejection),
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error, ErrorCode)]
124pub enum HandleRejection {
125 #[error("the handle is taken")]
127 Taken,
128 #[error("the handle is not allowed")]
131 NotAllowed,
132 #[error("the inbox claimed a handle too recently")]
134 TooSoon,
135}
136
137impl RetryableError for ApiError {
138 fn is_retryable(&self) -> bool {
139 match self {
140 Self::Auth(e) => retryable!(e),
141 Self::Preflight(e) => retryable!(e),
142 Self::Api(e) => retryable!(e),
143 _ => false,
144 }
145 }
146}
147
148#[derive(Debug)]
149pub struct ApiClientWrapper<ApiClient> {
150 pub api_client: preflight::GuardedApi<ApiClient>,
151 pub(crate) retry_strategy: Arc<Retry<ExponentialBackoff>>,
152 pub(crate) inbox_id: Option<String>,
153 pub(crate) configuration: Arc<xmtp_configuration::ServerConfiguration>,
158}
159
160impl<A> Clone for ApiClientWrapper<A> {
161 fn clone(&self) -> Self {
162 Self {
163 api_client: self.api_client.clone(),
164 retry_strategy: self.retry_strategy.clone(),
165 inbox_id: self.inbox_id.clone(),
166 configuration: self.configuration.clone(),
167 }
168 }
169}
170
171impl<ApiClient> ApiClientWrapper<ApiClient> {
172 pub fn new(api_client: ApiClient, retry_strategy: Retry<ExponentialBackoff>) -> Self {
173 Self {
174 api_client: preflight::GuardedApi::new(api_client),
175 retry_strategy: retry_strategy.into(),
176 inbox_id: None,
177 configuration: Arc::default(),
178 }
179 }
180
181 pub fn set_configuration(
184 &mut self,
185 configuration: Arc<xmtp_configuration::ServerConfiguration>,
186 ) {
187 self.configuration = configuration;
188 }
189
190 pub fn configuration(&self) -> &xmtp_configuration::ServerConfiguration {
192 &self.configuration
193 }
194
195 pub fn limits(&self) -> &xmtp_configuration::LimitsConfiguration {
197 &self.configuration.limits
198 }
199
200 pub fn attach_inbox_id(&mut self, inbox_id: Option<String>) {
203 self.inbox_id = inbox_id;
204 }
205}
206
207xmtp_common::if_native! {
208 #[cfg(test)]
209 #[ctor::ctor(unsafe)]
210 fn _setup() {
211 xmtp_common::logger()
212 }
213}
214
215#[cfg(test)]
216mod tests;