dptree/handler/
filter_map.rs

1use crate::{
2    di::{Asyncify, Injectable},
3    from_fn_with_description, Handler, HandlerDescription, HandlerSignature, Type,
4};
5
6use std::{collections::BTreeSet, iter::FromIterator, ops::ControlFlow, sync::Arc};
7
8/// Constructs a handler that optionally passes a value of a new type further.
9///
10/// If the `proj` function returns `Some(v)` then `v` will be added to the
11/// container and passed further in a handler chain. If the function returns
12/// `None`, then the handler will return [`ControlFlow::Continue`] with the old
13/// container.
14#[must_use]
15#[track_caller]
16pub fn filter_map<'a, Projection, Output, NewType, Args, Descr>(
17    proj: Projection,
18) -> Handler<'a, Output, Descr>
19where
20    Asyncify<Projection>: Injectable<Option<NewType>, Args> + Send + Sync + 'a,
21    Output: 'a,
22    Descr: HandlerDescription,
23    NewType: Send + Sync + 'static,
24{
25    filter_map_with_description(Descr::filter_map(), proj)
26}
27
28/// The asynchronous version of [`filter_map`].
29#[must_use]
30#[track_caller]
31pub fn filter_map_async<'a, Projection, Output, NewType, Args, Descr>(
32    proj: Projection,
33) -> Handler<'a, Output, Descr>
34where
35    Projection: Injectable<Option<NewType>, Args> + Send + Sync + 'a,
36    Output: 'a,
37    Descr: HandlerDescription,
38    NewType: Send + Sync + 'static,
39{
40    filter_map_async_with_description(Descr::filter_map_async(), proj)
41}
42
43/// [`filter_map`] with a custom description.
44#[must_use]
45#[track_caller]
46pub fn filter_map_with_description<'a, Projection, Output, NewType, Args, Descr>(
47    description: Descr,
48    proj: Projection,
49) -> Handler<'a, Output, Descr>
50where
51    Asyncify<Projection>: Injectable<Option<NewType>, Args> + Send + Sync + 'a,
52    Output: 'a,
53    NewType: Send + Sync + 'static,
54{
55    filter_map_async_with_description(description, Asyncify(proj))
56}
57
58/// [`filter_map_async`] with a custom description.
59#[must_use]
60#[track_caller]
61pub fn filter_map_async_with_description<'a, Projection, Output, NewType, Args, Descr>(
62    description: Descr,
63    proj: Projection,
64) -> Handler<'a, Output, Descr>
65where
66    Projection: Injectable<Option<NewType>, Args> + Send + Sync + 'a,
67    Output: 'a,
68    NewType: Send + Sync + 'static,
69{
70    let proj = Arc::new(proj);
71
72    from_fn_with_description(
73        description,
74        move |container, cont| {
75            let proj = Arc::clone(&proj);
76
77            async move {
78                let proj = proj.inject(&container);
79                let res = proj().await;
80                std::mem::drop(proj);
81
82                match res {
83                    Some(new_type) => {
84                        let mut intermediate = container.clone();
85                        intermediate.insert(new_type);
86                        match cont(intermediate).await {
87                            ControlFlow::Continue(_) => ControlFlow::Continue(container),
88                            ControlFlow::Break(result) => ControlFlow::Break(result),
89                        }
90                    }
91                    None => ControlFlow::Continue(container),
92                }
93            }
94        },
95        HandlerSignature::Other {
96            obligations: Projection::obligations(),
97            guaranteed_outcomes: BTreeSet::from_iter(vec![Type::of::<NewType>()]),
98            conditional_outcomes: BTreeSet::default(),
99            continues: true,
100        },
101    )
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::{deps, help_inference};
108
109    #[tokio::test]
110    async fn test_some() {
111        let value = 123;
112
113        let result = help_inference(filter_map(move || Some(value)))
114            .endpoint(move |event: i32| async move {
115                assert_eq!(event, value);
116                value
117            })
118            .dispatch(deps![])
119            .await;
120
121        assert!(result == ControlFlow::Break(value));
122    }
123
124    #[tokio::test]
125    async fn test_none() {
126        let result = help_inference(filter_map(|| None::<i32>))
127            .endpoint(|| async move { unreachable!() })
128            .dispatch(deps![])
129            .await;
130
131        assert!(result == ControlFlow::Continue(crate::deps![]));
132    }
133}