xmtp_proto/traits/combinators/
ignore.rs

1use std::marker::PhantomData;
2
3use crate::api::{ApiClientError, Client, Endpoint, Query, QueryRaw};
4
5/// Concrete type of the [`ignore`] combinator
6pub struct Ignore<E> {
7    endpoint: E,
8}
9
10#[xmtp_common::async_trait]
11impl<E, C> Query<C> for Ignore<E>
12where
13    E: QueryRaw<C>,
14    C: Client,
15{
16    type Output = ();
17    async fn query(&mut self, client: &C) -> Result<(), ApiClientError<C::Error>> {
18        let _ = QueryRaw::<C>::query_raw(&mut self.endpoint, client).await?;
19        // ignore response value
20        Ok(())
21    }
22}
23
24pub struct IgnoreSpecialized<S> {
25    _marker: PhantomData<S>,
26}
27
28impl<S, E: Endpoint<S>> Endpoint<IgnoreSpecialized<S>> for Ignore<E> {
29    type Output = <E as Endpoint<S>>::Output;
30
31    fn grpc_endpoint(&self) -> std::borrow::Cow<'static, str> {
32        self.endpoint.grpc_endpoint()
33    }
34
35    fn body(&self) -> Result<bytes::Bytes, crate::api::BodyError> {
36        self.endpoint.body()
37    }
38}
39
40//TODO: figure out how to skip deserialization of body
41//would require a query that doesn't deserialize?
42/// Ignore/drop the response data for this endpoint
43/// does not ignore any errors that might have occurred as a result of
44/// making a network request.
45/// the response body still must be valid protobuf
46pub fn ignore<E>(endpoint: E) -> Ignore<E> {
47    Ignore { endpoint }
48}
49
50#[cfg(test)]
51mod tests {
52    use prost::Message;
53
54    use super::*;
55    use crate::api::{
56        self, EndpointExt,
57        mock::{MockError, MockNetworkClient, TestEndpoint},
58    };
59    use rstest::*;
60
61    #[derive(prost::Message)]
62    struct TestProto {
63        #[prost(int64, tag = "1")]
64        inner: i64,
65    }
66
67    #[fixture]
68    fn client() -> MockNetworkClient {
69        let mut client = MockNetworkClient::new();
70        let bytes = TestProto { inner: 900 }.encode_to_vec();
71        client.expect_request().times(3).returning(|_, _, _| {
72            tracing::info!("error");
73            Err(ApiClientError::Client {
74                source: MockError::ARetryableError,
75            })
76        });
77        client
78            .expect_request()
79            .times(1)
80            .returning(move |_, _, _| Ok(http::Response::new(bytes.clone().into())));
81        client
82    }
83
84    #[xmtp_common::test]
85    async fn ignores_payloads() {
86        let mut client = MockNetworkClient::new();
87        let bytes = vec![0, 1, 2];
88        client
89            .expect_request()
90            .times(1)
91            .returning(move |_, _, _| Ok(http::Response::new(bytes.clone().into())));
92        let result: Result<(), _> = TestEndpoint.ignore_response().query(&client).await;
93        assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
94    }
95
96    #[rstest]
97    #[xmtp_common::test]
98    async fn ignore_is_retryable(client: MockNetworkClient) {
99        let result: Result<(), _> = api::ignore(api::retry(TestEndpoint)).query(&client).await;
100        assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
101    }
102
103    #[rstest]
104    #[xmtp_common::test]
105    async fn ignore_is_orthogonal(client: MockNetworkClient) {
106        let result: Result<(), _> = api::retry(api::ignore(TestEndpoint)).query(&client).await;
107        assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
108    }
109
110    #[rstest]
111    #[xmtp_common::test]
112    async fn endpoint_chains_work(client: MockNetworkClient) {
113        let result: Result<(), _> = TestEndpoint.ignore_response().retry().query(&client).await;
114        assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
115    }
116
117    #[rstest]
118    #[xmtp_common::test]
119    async fn endpoint_chains_orthogonal(client: MockNetworkClient) {
120        let result: Result<(), _> = TestEndpoint.retry().ignore_response().query(&client).await;
121        assert!(result.is_ok(), "{}", result.unwrap_err().to_string());
122    }
123
124    #[xmtp_common::test]
125    fn test_body_delegates_to_wrapped_endpoint() {
126        let ignore_endpoint = ignore(TestEndpoint);
127        let result = ignore_endpoint.body();
128        assert!(result.is_ok());
129        assert_eq!(result.unwrap(), bytes::Bytes::from(vec![]));
130    }
131
132    #[xmtp_common::test]
133    fn test_grpc_endpoint_delegates_to_wrapped_endpoint() {
134        let ignore_endpoint = ignore(TestEndpoint);
135        assert_eq!(ignore_endpoint.grpc_endpoint(), "");
136    }
137}