1use crate::{
4 api::{FakeEmptyStream, RetryQuery, XmtpStream, combinators::Ignore},
5 api_client::{AggregateStats, ApiStats, IdentityStats},
6};
7use futures::Stream;
8use http::{request, uri::PathAndQuery};
9use prost::bytes::Bytes;
10use std::{borrow::Cow, pin::Pin, sync::Arc};
11use xmtp_common::{BoxDynStream, MaybeSend, MaybeSync, Retry};
12
13xmtp_common::if_test! {
14 pub mod mock;
15}
16
17mod boxed_client;
18pub(super) mod combinators;
19mod error;
20mod query;
21pub mod short_hex;
22pub mod stream;
23pub use boxed_client::*;
24pub use error::*;
25
26pub trait HasStats {
27 fn aggregate_stats(&self) -> AggregateStats;
28 fn mls_stats(&self) -> ApiStats;
29 fn identity_stats(&self) -> IdentityStats;
30}
31
32impl<T: HasStats + ?Sized> HasStats for Arc<T> {
33 fn aggregate_stats(&self) -> AggregateStats {
34 (**self).aggregate_stats()
35 }
36 fn mls_stats(&self) -> ApiStats {
37 (**self).mls_stats()
38 }
39 fn identity_stats(&self) -> IdentityStats {
40 (**self).identity_stats()
41 }
42}
43
44pub trait Endpoint<Specialized = ()>: MaybeSend + MaybeSync {
47 type Output: MaybeSend + MaybeSync;
48 fn grpc_endpoint(&self) -> Cow<'static, str>;
49
50 fn body(&self) -> Result<Bytes, BodyError>;
51}
52
53pub trait EndpointExt<S>: Endpoint<S> {
54 fn ignore_response(self) -> Ignore<Self>
55 where
56 Self: Sized + Endpoint<S>,
57 {
58 combinators::ignore(self)
59 }
60
61 fn retry(self) -> RetryQuery<Self>
62 where
63 Self: Sized + Endpoint<S>,
64 {
65 combinators::retry(self)
66 }
67
68 fn retry_with_strategy<St>(self, strategy: Retry<St>) -> RetryQuery<Self, St>
69 where
70 Self: Sized + Endpoint<S>,
71 {
72 combinators::retry_with_strategy(self, strategy)
73 }
74}
75
76impl<S, E> EndpointExt<S> for E where E: Endpoint<S> {}
77
78pub struct BytesStream {
83 stream: BoxDynStream<'static, Result<Bytes, ApiClientError>>,
84}
85
86impl BytesStream {
87 pub fn new(
88 stream: impl Stream<Item = Result<Bytes, ApiClientError>> + MaybeSend + 'static,
89 ) -> Self {
90 Self {
91 stream: Box::pin(stream),
92 }
93 }
94}
95
96impl BytesStream {
97 fn stream(
98 self: Pin<&mut Self>,
99 ) -> Pin<&mut BoxDynStream<'static, Result<Bytes, ApiClientError>>> {
100 unsafe { self.map_unchecked_mut(|s| &mut s.stream) }
103 }
104}
105
106impl Stream for BytesStream {
107 type Item = Result<Bytes, ApiClientError>;
108
109 fn poll_next(
110 self: std::pin::Pin<&mut Self>,
111 cx: &mut std::task::Context<'_>,
112 ) -> std::task::Poll<Option<Self::Item>> {
113 self.stream().poll_next(cx)
114 }
115}
116
117#[xmtp_common::async_trait]
124pub trait Client: MaybeSend + MaybeSync {
125 fn host(&self) -> &str;
131
132 fn has_credential_source(&self) -> bool {
137 false
138 }
139
140 async fn request(
141 &self,
142 request: request::Builder,
143 path: PathAndQuery,
144 body: Bytes,
145 ) -> Result<http::Response<Bytes>, ApiClientError>;
146
147 async fn stream(
148 &self,
149 request: request::Builder,
150 path: http::uri::PathAndQuery,
151 body: Bytes,
152 ) -> Result<http::Response<BytesStream>, ApiClientError>;
153
154 async fn bidi_stream(
159 &self,
160 request: request::Builder,
161 path: http::uri::PathAndQuery,
162 body: BoxDynStream<'static, Bytes>,
163 ) -> Result<http::Response<BytesStream>, ApiClientError> {
164 let _ = (request, path, body);
165 Err(ApiClientError::OtherUnretryable(
166 "bidirectional streaming is not supported by this transport".into(),
167 ))
168 }
169
170 fn fake_stream(&self) -> http::Response<BytesStream> {
172 let fake = FakeEmptyStream::new();
173 let mut response = http::Response::new(BytesStream::new(fake));
174 if cfg!(target_arch = "wasm32") {
175 *response.version_mut() = http::version::Version::HTTP_11;
176 } else {
177 *response.version_mut() = http::version::Version::HTTP_2;
178 }
179
180 response
181 }
182}
183
184#[xmtp_common::async_trait]
185impl<T: MaybeSend + MaybeSync + ?Sized> Client for &T
186where
187 T: Client,
188{
189 fn host(&self) -> &str {
190 (**self).host()
191 }
192
193 fn has_credential_source(&self) -> bool {
194 (**self).has_credential_source()
195 }
196
197 async fn request(
198 &self,
199 request: request::Builder,
200 path: PathAndQuery,
201 body: Bytes,
202 ) -> Result<http::Response<Bytes>, ApiClientError> {
203 (**self).request(request, path, body).await
204 }
205
206 async fn stream(
207 &self,
208 request: request::Builder,
209 path: http::uri::PathAndQuery,
210 body: Bytes,
211 ) -> Result<http::Response<BytesStream>, ApiClientError> {
212 (**self).stream(request, path, body).await
213 }
214
215 async fn bidi_stream(
216 &self,
217 request: request::Builder,
218 path: http::uri::PathAndQuery,
219 body: BoxDynStream<'static, Bytes>,
220 ) -> Result<http::Response<BytesStream>, ApiClientError> {
221 (**self).bidi_stream(request, path, body).await
222 }
223}
224
225#[xmtp_common::async_trait]
226impl<T: MaybeSend + MaybeSync + ?Sized> Client for Box<T>
227where
228 T: Client,
229{
230 fn host(&self) -> &str {
231 (**self).host()
232 }
233
234 fn has_credential_source(&self) -> bool {
235 (**self).has_credential_source()
236 }
237
238 async fn request(
239 &self,
240 request: request::Builder,
241 path: PathAndQuery,
242 body: Bytes,
243 ) -> Result<http::Response<Bytes>, ApiClientError> {
244 (**self).request(request, path, body).await
245 }
246
247 async fn stream(
248 &self,
249 request: request::Builder,
250 path: http::uri::PathAndQuery,
251 body: Bytes,
252 ) -> Result<http::Response<BytesStream>, ApiClientError> {
253 (**self).stream(request, path, body).await
254 }
255
256 async fn bidi_stream(
257 &self,
258 request: request::Builder,
259 path: http::uri::PathAndQuery,
260 body: BoxDynStream<'static, Bytes>,
261 ) -> Result<http::Response<BytesStream>, ApiClientError> {
262 (**self).bidi_stream(request, path, body).await
263 }
264}
265
266#[xmtp_common::async_trait]
267impl<T: MaybeSend + MaybeSync + ?Sized> Client for Arc<T>
268where
269 T: Client,
270{
271 fn host(&self) -> &str {
272 (**self).host()
273 }
274
275 fn has_credential_source(&self) -> bool {
276 (**self).has_credential_source()
277 }
278
279 async fn request(
280 &self,
281 request: request::Builder,
282 path: PathAndQuery,
283 body: Bytes,
284 ) -> Result<http::Response<Bytes>, ApiClientError> {
285 (**self).request(request, path, body).await
286 }
287
288 async fn stream(
289 &self,
290 request: request::Builder,
291 path: PathAndQuery,
292 body: Bytes,
293 ) -> Result<http::Response<BytesStream>, ApiClientError> {
294 (**self).stream(request, path, body).await
295 }
296
297 async fn bidi_stream(
298 &self,
299 request: request::Builder,
300 path: PathAndQuery,
301 body: BoxDynStream<'static, Bytes>,
302 ) -> Result<http::Response<BytesStream>, ApiClientError> {
303 (**self).bidi_stream(request, path, body).await
304 }
305}
306
307#[xmtp_common::async_trait]
308pub trait IsConnectedCheck: MaybeSend + MaybeSync {
309 async fn is_connected(&self) -> bool;
311}
312
313#[xmtp_common::async_trait]
314impl<T: MaybeSend + MaybeSync + ?Sized> IsConnectedCheck for Arc<T>
315where
316 T: IsConnectedCheck,
317{
318 async fn is_connected(&self) -> bool {
319 (**self).is_connected().await
320 }
321}
322
323#[xmtp_common::async_trait]
324impl<T: MaybeSend + MaybeSync + ?Sized> IsConnectedCheck for Box<T>
325where
326 T: IsConnectedCheck,
327{
328 async fn is_connected(&self) -> bool {
329 (**self).is_connected().await
330 }
331}
332
333#[xmtp_common::async_trait]
336pub trait Query<C: Client>: MaybeSend + MaybeSync {
337 type Output: MaybeSend + MaybeSync;
338 async fn query(&mut self, client: &C) -> Result<Self::Output, ApiClientError>;
339}
340
341#[xmtp_common::async_trait]
342pub trait QueryRaw<C: Client>: MaybeSend + MaybeSync {
343 async fn query_raw(&mut self, client: &C) -> Result<bytes::Bytes, ApiClientError>;
344}
345
346#[xmtp_common::async_trait]
351pub trait QueryStream<T, C>
352where
353 C: Client,
354{
355 async fn stream(&mut self, client: &C) -> Result<XmtpStream<T>, ApiClientError>;
358
359 fn fake_stream(&mut self, client: &C) -> XmtpStream<T>;
360}
361
362#[xmtp_common::async_trait]
363pub trait QueryStreamExt<T, C: Client> {
364 async fn subscribe(&mut self, client: &C) -> Result<XmtpStream<T>, ApiClientError>
366 where
367 T: Default + prost::Message + 'static;
368}
369
370#[xmtp_common::async_trait]
371impl<T, C, E> QueryStreamExt<T, C> for E
372where
373 C: Client,
374 E: Endpoint<Output = T>,
375{
376 async fn subscribe(&mut self, client: &C) -> Result<XmtpStream<T>, ApiClientError>
377 where
378 T: Default + prost::Message + 'static,
379 {
380 self.stream(client).await
381 }
382}
383
384#[cfg(test)]
385mod test {
386 use crate::api::{
387 EndpointExt, Query,
388 mock::{MockNetworkClient, TestEndpoint},
389 };
390
391 #[xmtp_common::test]
393 async fn endpoints_can_be_chained() {
394 let client = MockNetworkClient::new();
395 std::mem::drop(TestEndpoint.ignore_response().retry().query(&client));
396 std::mem::drop(TestEndpoint.retry().ignore_response().query(&client));
397 }
398}