Skip to main content

rama_net/client/
conn.rs

1use core::fmt;
2
3use rama_core::{Service, extensions::ExtensionsRef, service::BoxService};
4
5use super::ConnectionError;
6
7#[derive(Clone)]
8/// The established connection to a server returned for the http client to be used.
9pub struct EstablishedClientConnection<S, Input> {
10    /// The `Input` for which a connection was established.
11    pub input: Input,
12    /// The established connection stream/service/... to the server.
13    pub conn: S,
14}
15
16impl<S: fmt::Debug, Input: fmt::Debug> fmt::Debug for EstablishedClientConnection<S, Input> {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        f.debug_struct("EstablishedClientConnection")
19            .field("input", &self.input)
20            .field("conn", &self.conn)
21            .finish()
22    }
23}
24
25/// Glue trait that is used as the Connector trait bound for
26/// clients establishing a connection on one layer or another.
27///
28/// Can also be manually implemented as an alternative [`Service`] trait,
29/// but from a Rama POV it is mostly used for UX trait bounds.
30pub trait ConnectorService<Input>: Send + Sync + 'static {
31    /// Connection returned by the [`ConnectorService`]
32    type Connection: Send + ExtensionsRef;
33
34    /// Establish a connection, which often involves some kind of handshake,
35    /// or connection revival.
36    ///
37    /// Service-specific errors are normalized into [`ConnectionError`] at this
38    /// boundary so connector combinators can reason about connection failures
39    /// without knowing every concrete error type in the stack.
40    fn connect(
41        &self,
42        input: Input,
43    ) -> impl Future<
44        Output = Result<EstablishedClientConnection<Self::Connection, Input>, ConnectionError>,
45    > + Send
46    + '_;
47}
48
49impl<S, Input, Connection> ConnectorService<Input> for S
50where
51    S: Service<
52            Input,
53            Output = EstablishedClientConnection<Connection, Input>,
54            Error: Into<ConnectionError>,
55        >,
56    Connection: Send + ExtensionsRef,
57{
58    type Connection = Connection;
59
60    fn connect(
61        &self,
62        input: Input,
63    ) -> impl Future<
64        Output = Result<EstablishedClientConnection<Self::Connection, Input>, ConnectionError>,
65    > + Send
66    + '_ {
67        let future = self.serve(input);
68        async move { future.await.map_err(Into::into) }
69    }
70}
71
72/// A [`ConnectorService`] which only job is to [`Box`]
73/// the created [`Service`] by the inner [`ConnectorService`].
74#[derive(Debug, Clone)]
75pub struct BoxedConnectorService<S>(S);
76
77impl<S> BoxedConnectorService<S> {
78    /// Create a new [`BoxedConnectorService`].
79    pub fn new(connector: S) -> Self {
80        Self(connector)
81    }
82}
83
84impl<S, Input, Svc> Service<Input> for BoxedConnectorService<S>
85where
86    S: ConnectorService<Input, Connection = Svc>,
87    Svc: Service<Input>,
88    Input: Send + 'static,
89{
90    type Output = EstablishedClientConnection<BoxService<Input, Svc::Output, Svc::Error>, Input>;
91    type Error = ConnectionError;
92
93    async fn serve(&self, input: Input) -> Result<Self::Output, Self::Error> {
94        let EstablishedClientConnection { input, conn: svc } = self.0.connect(input).await?;
95        Ok(EstablishedClientConnection {
96            input,
97            conn: svc.boxed(),
98        })
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use core::{convert::Infallible, fmt};
105
106    use rama_core::ServiceInput;
107
108    use super::*;
109    use crate::client::{ConnectionErrorDomain, ConnectionErrorKind};
110
111    #[derive(Debug)]
112    struct LegacyError;
113
114    impl fmt::Display for LegacyError {
115        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116            f.write_str("legacy connector error")
117        }
118    }
119
120    impl core::error::Error for LegacyError {}
121
122    #[derive(Debug)]
123    struct LegacyFailingConnector;
124
125    impl Service<()> for LegacyFailingConnector {
126        type Output = EstablishedClientConnection<ServiceInput<()>, ()>;
127        type Error = rama_core::error::BoxError;
128
129        async fn serve(&self, _input: ()) -> Result<Self::Output, Self::Error> {
130            Err(Box::new(LegacyError))
131        }
132    }
133
134    #[derive(Debug)]
135    struct ClassifiedFailingConnector;
136
137    impl Service<()> for ClassifiedFailingConnector {
138        type Output = EstablishedClientConnection<ServiceInput<()>, ()>;
139        type Error = ConnectionError;
140
141        async fn serve(&self, _input: ()) -> Result<Self::Output, Self::Error> {
142            Err(ConnectionError::transport(
143                LegacyError,
144                ConnectionErrorKind::Unavailable,
145            ))
146        }
147    }
148
149    #[derive(Debug)]
150    struct SuccessfulConnector;
151
152    impl Service<usize> for SuccessfulConnector {
153        type Output = EstablishedClientConnection<ServiceInput<()>, usize>;
154        type Error = Infallible;
155
156        async fn serve(&self, input: usize) -> Result<Self::Output, Self::Error> {
157            Ok(EstablishedClientConnection {
158                input,
159                conn: ServiceInput::new(()),
160            })
161        }
162    }
163
164    #[tokio::test]
165    async fn connector_service_normalizes_legacy_errors() {
166        let error = LegacyFailingConnector.connect(()).await.unwrap_err();
167
168        assert_eq!(error.domain(), ConnectionErrorDomain::Unknown);
169        assert_eq!(error.kind(), ConnectionErrorKind::Other);
170        assert_eq!(error.to_string(), "legacy connector error");
171    }
172
173    #[tokio::test]
174    async fn connector_service_preserves_classified_errors() {
175        let error = ClassifiedFailingConnector.connect(()).await.unwrap_err();
176
177        assert_eq!(error.domain(), ConnectionErrorDomain::Transport);
178        assert_eq!(error.kind(), ConnectionErrorKind::Unavailable);
179        assert_eq!(error.to_string(), "legacy connector error");
180    }
181
182    #[tokio::test]
183    async fn connector_service_preserves_successful_input() {
184        let established = SuccessfulConnector.connect(42).await.unwrap();
185
186        assert_eq!(established.input, 42);
187    }
188}