Skip to main content

ntex_util/future/
either.rs

1use std::{error, fmt, future::Future, io, pin::Pin, task::Context, task::Poll};
2
3use ntex_service::{BindCtx, Ctx, Service};
4
5/// A value of one of two types.
6///
7/// `Either` is a [`Future`] when both variants are futures with the same
8/// output, and a [`Service`] when both variants are services with the same
9/// request, response, and error types.
10#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
11pub enum Either<A, B> {
12    /// First branch of the type
13    Left(A),
14    /// Second branch of the type
15    Right(B),
16}
17
18impl<A, B> Either<A, B> {
19    fn project(self: Pin<&mut Self>) -> Either<Pin<&mut A>, Pin<&mut B>> {
20        unsafe {
21            match self.get_unchecked_mut() {
22                Either::Left(a) => Either::Left(Pin::new_unchecked(a)),
23                Either::Right(b) => Either::Right(Pin::new_unchecked(b)),
24            }
25        }
26    }
27
28    #[inline]
29    /// Returns `true` if the value is the `Left` variant.
30    pub fn is_left(&self) -> bool {
31        match *self {
32            Either::Left(_) => true,
33            Either::Right(_) => false,
34        }
35    }
36
37    #[inline]
38    /// Returns `true` if the value is the `Right` variant.
39    pub fn is_right(&self) -> bool {
40        !self.is_left()
41    }
42
43    #[inline]
44    /// Converts the left side of `Either<A, B>` to an `Option<A>`.
45    pub fn left(self) -> Option<A> {
46        match self {
47            Either::Left(l) => Some(l),
48            Either::Right(_) => None,
49        }
50    }
51
52    #[inline]
53    /// Converts the right side of `Either<A, B>` to an `Option<B>`.
54    pub fn right(self) -> Option<B> {
55        match self {
56            Either::Left(_) => None,
57            Either::Right(r) => Some(r),
58        }
59    }
60
61    #[inline]
62    /// Converts `&Either<A, B>` to `Either<&A, &B>`.
63    pub fn as_ref(&self) -> Either<&A, &B> {
64        match *self {
65            Either::Left(ref inner) => Either::Left(inner),
66            Either::Right(ref inner) => Either::Right(inner),
67        }
68    }
69
70    #[inline]
71    /// Converts `&mut Either<A, B>` to `Either<&mut A, &mut B>`.
72    pub fn as_mut(&mut self) -> Either<&mut A, &mut B> {
73        match *self {
74            Either::Left(ref mut inner) => Either::Left(inner),
75            Either::Right(ref mut inner) => Either::Right(inner),
76        }
77    }
78}
79
80impl<T> Either<T, T> {
81    #[inline]
82    /// Extracts the value of an `Either` over two equal types.
83    pub fn into_inner(self) -> T {
84        match self {
85            Either::Left(x) | Either::Right(x) => x,
86        }
87    }
88}
89
90/// `Either` implements `Error` if *both* `A` and `B` implement it.
91impl<A, B> error::Error for Either<A, B>
92where
93    A: error::Error,
94    B: error::Error,
95{
96    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
97        match self {
98            Either::Left(a) => a.source(),
99            Either::Right(b) => b.source(),
100        }
101    }
102}
103
104impl<A, B> fmt::Display for Either<A, B>
105where
106    A: fmt::Display,
107    B: fmt::Display,
108{
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            Either::Left(a) => a.fmt(f),
112            Either::Right(b) => b.fmt(f),
113        }
114    }
115}
116
117impl<A, B> Future for Either<A, B>
118where
119    A: Future,
120    B: Future<Output = A::Output>,
121{
122    type Output = A::Output;
123
124    #[inline]
125    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
126        match self.project() {
127            Either::Left(x) => x.poll(cx),
128            Either::Right(x) => x.poll(cx),
129        }
130    }
131}
132
133impl<E: error::Error> From<Either<E, io::Error>> for io::Error {
134    fn from(err: Either<E, io::Error>) -> Self {
135        match err {
136            Either::Left(e) => io::Error::other(format!("{e:?}")),
137            Either::Right(e) => e,
138        }
139    }
140}
141
142impl<A, B, St, Req> Service<St, Req> for Either<A, B>
143where
144    A: Service<St, Req>,
145    B: Service<St, Req, Res = A::Res, Error = A::Error>,
146{
147    type Res = A::Res;
148    type Error = A::Error;
149
150    fn bind(&self, ctx: BindCtx<'_>) {
151        match self {
152            Either::Left(svc) => ctx.bind(svc),
153            Either::Right(svc) => ctx.bind(svc),
154        }
155    }
156
157    async fn call(&self, req: Req, ctx: Ctx<'_, Self, St>) -> Result<Self::Res, Self::Error> {
158        match self {
159            Either::Left(svc) => ctx.call(svc, req).await,
160            Either::Right(svc) => ctx.call(svc, req).await,
161        }
162    }
163
164    async fn ready(&self, ctx: Ctx<'_, Self, St>) -> Result<(), Self::Error> {
165        match self {
166            Either::Left(svc) => ctx.ready(svc).await,
167            Either::Right(svc) => ctx.ready(svc).await,
168        }
169    }
170
171    async fn shutdown(&self, ctx: Ctx<'_, Self, St>) {
172        match self {
173            Either::Left(svc) => ctx.shutdown(svc).await,
174            Either::Right(svc) => ctx.shutdown(svc).await,
175        }
176    }
177}
178
179#[cfg(test)]
180mod test {
181    use std::{cell::Cell, error::Error as _, future, rc::Rc};
182
183    use ntex_service::{Pipeline, ReadyBinding, ReadyNotifier};
184
185    use super::*;
186
187    /// STATEMENT: `Either` accessors, mutation, extraction, and display use the active branch.
188    ///
189    /// HOW:
190    /// - Inspect and mutate left and right values through borrowed accessors.
191    /// - Extract their values and compare inner-value and display results for both branches.
192    #[test]
193    fn accessors() {
194        let mut value = Either::<u8, String>::Left(10);
195        assert!(value.is_left());
196        assert!(!value.is_right());
197        assert_eq!(value.as_ref(), Either::Left(&10));
198        if let Either::Left(inner) = value.as_mut() {
199            *inner = 20;
200        }
201        assert_eq!(value.left(), Some(20));
202
203        let mut value = Either::<u8, String>::Right("right".to_owned());
204        assert!(!value.is_left());
205        assert!(value.is_right());
206        assert_eq!(value.as_ref(), Either::Right(&"right".to_owned()));
207        if let Either::Right(inner) = value.as_mut() {
208            inner.push_str(" branch");
209        }
210        assert_eq!(value.right().as_deref(), Some("right branch"));
211
212        assert_eq!(Either::<u8, u8>::Left(1).into_inner(), 1);
213        assert_eq!(Either::<u8, u8>::Right(2).into_inner(), 2);
214
215        assert_eq!(
216            format!("{}", Either::<_, &'static str>::Left("test")),
217            "test"
218        );
219        assert_eq!(
220            format!("{}", Either::<&'static str, _>::Right("test")),
221            "test"
222        );
223    }
224
225    /// STATEMENT: Awaiting `Either` returns the active future's output.
226    ///
227    /// HOW:
228    /// - Wrap ready futures in each branch and verify their distinct returned values.
229    #[ntex::test]
230    async fn future() {
231        let left: Either<_, future::Ready<u8>> = Either::Left(future::ready(10));
232        assert_eq!(left.await, 10);
233
234        let right: Either<future::Ready<u8>, _> = Either::Right(future::ready(20));
235        assert_eq!(right.await, 20);
236    }
237
238    #[derive(Debug)]
239    struct TestError {
240        message: &'static str,
241        source: Option<io::Error>,
242    }
243
244    impl fmt::Display for TestError {
245        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246            f.write_str(self.message)
247        }
248    }
249
250    impl error::Error for TestError {
251        fn source(&self) -> Option<&(dyn error::Error + 'static)> {
252            self.source
253                .as_ref()
254                .map(|source| source as &(dyn error::Error + 'static))
255        }
256    }
257
258    /// STATEMENT: `Either` forwards error messages and sources and preserves I/O conversion semantics.
259    ///
260    /// HOW:
261    /// - Inspect display and source chains for errors in both branches.
262    /// - Convert each branch to `io::Error` and check its kind and message.
263    #[test]
264    fn errors() {
265        let left = Either::<TestError, TestError>::Left(TestError {
266            message: "left",
267            source: Some(io::Error::other("left source")),
268        });
269        assert_eq!(left.to_string(), "left");
270        assert_eq!(left.source().unwrap().to_string(), "left source");
271
272        let right = Either::<TestError, TestError>::Right(TestError {
273            message: "right",
274            source: Some(io::Error::other("right source")),
275        });
276        assert_eq!(right.to_string(), "right");
277        assert_eq!(right.source().unwrap().to_string(), "right source");
278
279        let left: io::Error = Either::<TestError, io::Error>::Left(TestError {
280            message: "converted",
281            source: None,
282        })
283        .into();
284        assert_eq!(left.kind(), io::ErrorKind::Other);
285        assert!(left.to_string().contains("converted"));
286
287        let right: io::Error = Either::<TestError, io::Error>::Right(io::Error::new(
288            io::ErrorKind::TimedOut,
289            "right error",
290        ))
291        .into();
292        assert_eq!(right.kind(), io::ErrorKind::TimedOut);
293        assert_eq!(right.to_string(), "right error");
294    }
295
296    #[derive(Default)]
297    struct ServiceState {
298        bound: Cell<bool>,
299        ready: Cell<bool>,
300        failed: Cell<bool>,
301        called: Cell<bool>,
302        shutdown: Cell<bool>,
303        changed: ReadyNotifier,
304    }
305
306    struct TestService {
307        state: Rc<ServiceState>,
308        response: u8,
309    }
310
311    impl Service<(), u8> for TestService {
312        type Res = u8;
313        type Error = ();
314
315        fn bind(&self, ctx: BindCtx<'_>) {
316            self.state.bound.set(true);
317            ctx.subscribe(&self.state.changed);
318        }
319
320        async fn ready(&self, _: Ctx<'_, Self>) -> Result<(), Self::Error> {
321            self.state.ready.set(true);
322            if self.state.failed.get() { Err(()) } else { Ok(()) }
323        }
324
325        async fn call(&self, req: u8, _: Ctx<'_, Self>) -> Result<Self::Res, Self::Error> {
326            self.state.called.set(true);
327            Ok(req + self.response)
328        }
329
330        async fn shutdown(&self, _: Ctx<'_, Self>) {
331            self.state.shutdown.set(true);
332        }
333    }
334
335    /// STATEMENT: `Either` forwards binding, readiness, calls, and shutdown to its active service.
336    ///
337    /// HOW:
338    /// - Build pipelines for each branch using services with lifecycle flags.
339    /// - Check binding and calls, then notify a readiness failure and verify it reaches the pipeline.
340    /// - Restore readiness and verify shutdown reaches the active service.
341    #[ntex::test]
342    async fn service() {
343        async fn check(service: Either<TestService, TestService>, response: u8) {
344            let state = match &service {
345                Either::Left(service) | Either::Right(service) => service.state.clone(),
346            };
347            let service = Pipeline::new((), service);
348
349            assert!(state.bound.get());
350            assert_eq!(service.ready().await, Ok(()));
351            assert!(state.ready.get());
352            assert_eq!(service.call(5).await, Ok(5 + response));
353            assert!(state.called.get());
354
355            state.called.set(false);
356            state.failed.set(true);
357            state.changed.notify();
358            assert!(!service.is_ready());
359            assert_eq!(service.call(5).await, Err(()));
360            assert!(!state.called.get());
361            state.failed.set(false);
362            state.changed.notify();
363            assert_eq!(service.call(5).await, Ok(5 + response));
364
365            service.shutdown().await;
366            assert!(state.shutdown.get());
367        }
368
369        let state = Rc::new(ServiceState::default());
370        check(
371            Either::Left(TestService {
372                state,
373                response: 10,
374            }),
375            10,
376        )
377        .await;
378
379        let state = Rc::new(ServiceState::default());
380        check(
381            Either::Right(TestService {
382                state,
383                response: 20,
384            }),
385            20,
386        )
387        .await;
388    }
389
390    /// STATEMENT: Either branch binds an enclosed `ReadyBinding` before using its readiness scope.
391    ///
392    /// HOW:
393    /// - Place a bound service in each branch and construct an enclosing pipeline.
394    /// - Verify readiness and calls succeed without an unbound-dependency panic.
395    #[ntex::test]
396    async fn bound_service() {
397        for left in [true, false] {
398            let inner = ReadyBinding::new(TestService {
399                state: Rc::new(ServiceState::default()),
400                response: 10,
401            });
402            let service = if left {
403                Either::Left(inner)
404            } else {
405                Either::Right(inner)
406            };
407            let service = Pipeline::new((), service);
408            assert_eq!(service.ready().await, Ok(()));
409            assert_eq!(service.call(5).await, Ok(15));
410        }
411    }
412
413    /// STATEMENT: Branch-specific extraction returns `None` for the inactive `Either` branch.
414    ///
415    /// HOW:
416    /// - Call both extraction methods on left and right values and compare all four results.
417    #[test]
418    fn either_left_right() {
419        assert_eq!(Either::<u8, u8>::Left(1).left(), Some(1));
420        assert_eq!(Either::<u8, u8>::Left(1).right(), None);
421        assert_eq!(Either::<u8, u8>::Right(2).left(), None);
422        assert_eq!(Either::<u8, u8>::Right(2).right(), Some(2));
423    }
424}