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("signature verification returned {got} verdicts for {expected} signatures")]
108 VerdictCount { expected: usize, got: usize },
109 #[error(transparent)]
111 InvalidEnvelope(#[from] xmtp_mls_validation::ValidationError),
112 #[error(transparent)]
114 Envelope(#[from] xmtp_api_backend::envelope::EnvelopeError),
115 #[error(transparent)]
119 ProtoConversion(#[from] xmtp_proto::ConversionError),
120 #[error(transparent)]
122 #[error_code(inherit)]
123 HandleRejected(#[from] HandleRejection),
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error, ErrorCode)]
129pub enum HandleRejection {
130 #[error("the handle is taken")]
132 Taken,
133 #[error("the handle is not allowed")]
136 NotAllowed,
137 #[error("the inbox claimed a handle too recently")]
139 TooSoon,
140}
141
142impl RetryableError for ApiError {
143 fn is_retryable(&self) -> bool {
144 match self {
145 Self::Auth(e) => retryable!(e),
146 Self::Preflight(e) => retryable!(e),
147 Self::Api(e) => retryable!(e),
148 Self::VerdictCount { .. } => true,
149 _ => false,
150 }
151 }
152
153 fn rate_limit_delay(&self) -> Option<xmtp_common::time::Duration> {
155 match self {
156 Self::Api(e) => xmtp_api_grpc::error::rate_limit_delay(e),
157 Self::Preflight(e) => e.rate_limit_delay(),
158 _ => None,
159 }
160 }
161}
162
163#[derive(Debug)]
164pub struct ApiClientWrapper<ApiClient> {
165 pub api_client: preflight::GuardedApi<ApiClient>,
166 pub(crate) retry_strategy: Arc<Retry<ExponentialBackoff>>,
167 pub(crate) inbox_id: Option<String>,
168 pub(crate) configuration: Arc<xmtp_configuration::ServerConfiguration>,
173}
174
175impl<A> Clone for ApiClientWrapper<A> {
176 fn clone(&self) -> Self {
177 Self {
178 api_client: self.api_client.clone(),
179 retry_strategy: self.retry_strategy.clone(),
180 inbox_id: self.inbox_id.clone(),
181 configuration: self.configuration.clone(),
182 }
183 }
184}
185
186impl<ApiClient> ApiClientWrapper<ApiClient> {
187 pub fn new(api_client: ApiClient, retry_strategy: Retry<ExponentialBackoff>) -> Self {
188 Self {
189 api_client: preflight::GuardedApi::new(api_client),
190 retry_strategy: retry_strategy.into(),
191 inbox_id: None,
192 configuration: Arc::default(),
193 }
194 }
195
196 pub fn set_configuration(
199 &mut self,
200 configuration: Arc<xmtp_configuration::ServerConfiguration>,
201 ) {
202 self.configuration = configuration;
203 }
204
205 pub fn configuration(&self) -> &xmtp_configuration::ServerConfiguration {
207 &self.configuration
208 }
209
210 pub fn limits(&self) -> &xmtp_configuration::LimitsConfiguration {
212 &self.configuration.limits
213 }
214
215 pub fn attach_inbox_id(&mut self, inbox_id: Option<String>) {
218 self.inbox_id = inbox_id;
219 }
220}
221
222xmtp_common::if_native! {
223 #[cfg(test)]
224 #[ctor::ctor(unsafe)]
225 fn _setup() {
226 xmtp_common::logger()
227 }
228}
229
230#[cfg(test)]
231mod tests;