xmtp_proto/traits/
stream.rs1use std::{
4 marker::PhantomData,
5 pin::Pin,
6 task::{Context, Poll, ready},
7};
8
9use crate::{
10 ApiEndpoint,
11 api::{ApiClientError, BytesStream},
12};
13use futures::{Stream, TryStream};
14use pin_project::pin_project;
15
16mod fake_empty;
17pub use fake_empty::*;
18
19#[pin_project]
20pub struct XmtpStream<T> {
22 #[pin]
23 inner: BytesStream,
24 endpoint: ApiEndpoint,
25 _marker: PhantomData<T>,
26}
27
28impl<T> XmtpStream<T> {
29 pub fn new(inner: BytesStream, endpoint: ApiEndpoint) -> Self {
30 Self {
31 inner,
32 endpoint,
33 _marker: PhantomData,
34 }
35 }
36}
37
38impl<T> Stream for XmtpStream<T>
39where
40 T: prost::Message + Default,
41{
42 type Item = Result<T, ApiClientError>;
43
44 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
45 let this = self.as_mut().project();
46 if let Some(item) = ready!(this.inner.try_poll_next(cx)) {
47 let res = item
48 .map_err(|e| e.endpoint(self.endpoint.clone()))
49 .and_then(|i| T::decode(i).map_err(ApiClientError::DecodeError));
50 Poll::Ready(Some(res))
51 } else {
52 Poll::Ready(None)
53 }
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use crate::api::NetworkError;
60
61 use super::*;
62 use bytes::Bytes;
63 use futures::{StreamExt, pin_mut, stream};
64 use prost::Message;
65 use xmtp_common::RetryableError;
66
67 #[derive(prost::Message)]
68 struct TestMessage {
69 #[prost(string, tag = "1")]
70 content: String,
71 }
72
73 #[xmtp_common::test]
74 async fn test_poll_next_successful_decode() {
75 let test_message = TestMessage {
76 content: "test content".to_string(),
77 };
78 let encoded_bytes = test_message.encode_to_vec();
79
80 let inner_stream = BytesStream::new(stream::once(async move {
81 Ok::<Bytes, ApiClientError>(Bytes::from(encoded_bytes))
82 }));
83 let xmtp_stream =
84 XmtpStream::<TestMessage>::new(inner_stream, ApiEndpoint::SubscribeGroupMessages);
85 pin_mut!(xmtp_stream);
86
87 let result = xmtp_stream.next().await.unwrap();
88 assert!(result.is_ok());
89 let decoded_message = result.unwrap();
90 assert_eq!(decoded_message.content, "test content");
91 let n = xmtp_stream.next().await;
93 assert!(n.is_none());
94 }
95
96 #[derive(thiserror::Error, Debug)]
97 enum TestError {
98 #[error("test error")]
99 DummyError,
100 }
101
102 impl RetryableError for TestError {
103 fn is_retryable(&self) -> bool {
104 false
105 }
106 }
107
108 #[xmtp_common::test]
109 async fn test_poll_next_error_mapping() {
110 let inner_stream = BytesStream::new(stream::once(async {
111 Err::<Bytes, ApiClientError>(ApiClientError::Client {
112 source: NetworkError::new(TestError::DummyError),
113 })
114 }));
115 let xmtp_stream =
116 XmtpStream::<TestMessage>::new(inner_stream, ApiEndpoint::SubscribeGroupMessages);
117 pin_mut!(xmtp_stream);
118
119 let result = xmtp_stream.next().await.unwrap();
120 assert!(result.is_err());
121
122 match result {
123 Err(ApiClientError::ClientWithEndpoint { endpoint, .. }) => {
124 assert_eq!(endpoint, "subscribe_group_messages");
125 }
126 _ => panic!("Expected ClientWithEndpoint error"),
127 }
128 let n = xmtp_stream.next().await;
130 assert!(n.is_none());
131 }
132}