dptree/
lib.rs

1//! An implementation of the [chain (tree) of responsibility] pattern.
2//!
3//! [[`examples/web_server.rs`](https://github.com/teloxide/dptree/blob/master/examples/web_server.rs)]
4//! ```
5//! use dptree::prelude::*;
6//!
7//! type WebHandler = Endpoint<'static, String>;
8//!
9//! #[rustfmt::skip]
10//! #[tokio::main]
11//! async fn main() {
12//!     let web_server = dptree::entry()
13//!         .branch(smiles_handler())
14//!         .branch(sqrt_handler())
15//!         .branch(not_found_handler());
16//!     
17//!     assert_eq!(
18//!         web_server.dispatch(dptree::deps!["/smile"]).await,
19//!         ControlFlow::Break("🙃".to_owned())
20//!     );
21//!     assert_eq!(
22//!         web_server.dispatch(dptree::deps!["/sqrt 16"]).await,
23//!         ControlFlow::Break("4".to_owned())
24//!     );
25//!     assert_eq!(
26//!         web_server.dispatch(dptree::deps!["/lol"]).await,
27//!         ControlFlow::Break("404 Not Found".to_owned())
28//!     );
29//! }
30//!
31//! fn smiles_handler() -> WebHandler {
32//!     dptree::filter(|req: &'static str| req.starts_with("/smile"))
33//!         .endpoint(|| async { "🙃".to_owned() })
34//! }
35//!
36//! fn sqrt_handler() -> WebHandler {
37//!     dptree::filter_map(|req: &'static str| {
38//!         if req.starts_with("/sqrt") {
39//!             let (_, n) = req.split_once(' ')?;
40//!             n.parse::<f64>().ok()
41//!         } else {
42//!             None
43//!         }
44//!     })
45//!     .endpoint(|n: f64| async move { format!("{}", n.sqrt()) })
46//! }
47//!
48//! fn not_found_handler() -> WebHandler {
49//!     dptree::endpoint(|| async { "404 Not Found".to_owned() })
50//! }
51//! ```
52//!
53//! For a high-level overview, please see [`README.md`](https://github.com/teloxide/dptree).
54//!
55//! [chain (tree) of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
56
57mod handler;
58
59pub mod di;
60pub mod prelude;
61
62pub use handler::*;
63
64/// Filters an enumeration, passing its payload forwards.
65///
66/// This macro expands to a [`crate::Handler`] that acts on your enumeration
67/// type: if the enumeration is of a certain variant, the execution continues;
68/// otherwise, `dptree` will try the next branch. This is very useful for
69/// dialogue FSM transitions and incoming command filtering; for a real-world
70/// example, please see teloxide's [`examples/purchase.rs`].
71///
72/// Variants can take the following forms:
73///
74///  - `Enum::MyVariant` for empty variants;
75///  - `Enum::MyVariant(param1, ..., paramN)` for tuple-like variants;
76///  - `Enum::MyVariant { param1, ..., paramN }` for `struct`-like variants.
77///
78/// In the first case, this macro results in a simple [`crate::filter`]; in the
79/// second and third cases, this macro results in [`crate::filter_map`] that
80/// passes the payload of `MyVariant` to the next handler if the match occurs.
81/// (This next handler can be an endpoint or a more complex one.) The payload
82/// format depend on the form of `MyVariant`:
83///
84///  - For `Enum::MyVariant(param)` and `Enum::MyVariant { param }`, the payload
85///    is `param`.
86///  - For `Enum::MyVariant(param,)` and `Enum::MyVariant { param, }`, the
87///    payload is `(param,)`.
88///  - For `Enum::MyVariant(param1, ..., paramN)` and `Enum::MyVariant { param1,
89///    ..., paramN }`, the payload is `(param1, ..., paramN)` (where `N`>1).
90///
91/// ## Dependency requirements
92///
93///  - Your enumeration `Enum`.
94///
95/// ## Examples
96///
97/// ```
98/// use dptree::prelude::*;
99///
100/// # #[tokio::main]
101/// # async fn main() {
102/// #[derive(Clone)]
103/// enum Command {
104///     Meow,
105///     Add(i32, i32),
106/// }
107///
108/// let h: Handler<_> = dptree::entry()
109///     .branch(dptree::case![Command::Meow].endpoint(|| async move { format!("Meow!") }))
110///     .branch(
111///         dptree::case![Command::Add(x, y)]
112///             .endpoint(|(x, y): (i32, i32)| async move { format!("{}", x + y) }),
113///     );
114///
115/// assert_eq!(
116///     h.dispatch(dptree::deps![Command::Meow]).await,
117///     ControlFlow::Break("Meow!".to_owned())
118/// );
119/// assert_eq!(
120///     h.dispatch(dptree::deps![Command::Add(1, 2)]).await,
121///     ControlFlow::Break("3".to_owned())
122/// );
123/// # }
124/// ```
125///
126/// [`examples/purchase.rs`]: https://github.com/teloxide/teloxide/blob/master/examples/purchase.rs
127#[macro_export]
128macro_rules! case {
129    ($($variant:ident)::+) => {
130        $crate::filter(|x| matches!(x, $($variant)::+))
131    };
132    ($($variant:ident)::+ ($param:ident)) => {
133        $crate::filter_map(|x| match x {
134            $($variant)::+($param) => Some($param),
135            _ => None,
136        })
137    };
138    ($($variant:ident)::+ ($($param:ident),+ $(,)?)) => {
139        $crate::filter_map(|x| match x {
140            $($variant)::+($($param),+) => Some(($($param),+ ,)),
141            _ => None,
142        })
143    };
144    ($($variant:ident)::+ {$param:ident}) => {
145        $crate::filter_map(|x| match x {
146            $($variant)::+{$param} => Some($param),
147            _ => None,
148        })
149    };
150    ($($variant:ident)::+ {$($param:ident),+ $(,)?}) => {
151        $crate::filter_map(|x| match x {
152            $($variant)::+ { $($param),+ } => Some(($($param),+ ,)),
153            _ => None,
154        })
155    };
156}
157
158#[cfg(test)]
159mod tests {
160    use std::ops::ControlFlow;
161
162    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
163    enum State {
164        A,
165        B(i32),
166        C(i32, &'static str),
167        D { foo: i32 },
168        E { foo: i32, bar: &'static str },
169        Other,
170    }
171
172    #[tokio::test]
173    async fn handler_empty_variant() {
174        let input = State::A;
175        let h: crate::Handler<_> = case![State::A].endpoint(|| async move { 123 });
176
177        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
178        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
179    }
180
181    #[tokio::test]
182    async fn handler_single_fn_variant() {
183        let input = State::B(42);
184        let h: crate::Handler<_> = case![State::B(x)].endpoint(|x: i32| async move {
185            assert_eq!(x, 42);
186            123
187        });
188
189        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
190        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
191    }
192
193    #[tokio::test]
194    async fn handler_single_fn_variant_trailing_comma() {
195        let input = State::B(42);
196        let h: crate::Handler<_> = case![State::B(x,)].endpoint(|(x,): (i32,)| async move {
197            assert_eq!(x, 42);
198            123
199        });
200
201        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
202        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
203    }
204
205    #[tokio::test]
206    async fn handler_fn_variant() {
207        let input = State::C(42, "abc");
208        let h: crate::Handler<_> =
209            case![State::C(x, y)].endpoint(|(x, str): (i32, &'static str)| async move {
210                assert_eq!(x, 42);
211                assert_eq!(str, "abc");
212                123
213            });
214
215        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
216        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
217    }
218
219    #[tokio::test]
220    async fn handler_single_struct_variant() {
221        let input = State::D { foo: 42 };
222        let h: crate::Handler<_> = case![State::D { foo }].endpoint(|x: i32| async move {
223            assert_eq!(x, 42);
224            123
225        });
226
227        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
228        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
229    }
230
231    #[tokio::test]
232    async fn handler_single_struct_variant_trailing_comma() {
233        let input = State::D { foo: 42 };
234        #[rustfmt::skip] // rustfmt removes the trailing comma from `State::D { foo, }`, but it plays a vital role in this test.
235        let h: crate::Handler<_> = case![State::D { foo, }].endpoint(|(x,): (i32,)| async move {
236            assert_eq!(x, 42);
237            123
238        });
239
240        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
241        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
242    }
243
244    #[tokio::test]
245    async fn handler_struct_variant() {
246        let input = State::E { foo: 42, bar: "abc" };
247        let h: crate::Handler<_> =
248            case![State::E { foo, bar }].endpoint(|(x, str): (i32, &'static str)| async move {
249                assert_eq!(x, 42);
250                assert_eq!(str, "abc");
251                123
252            });
253
254        assert_eq!(h.dispatch(crate::deps![input]).await, ControlFlow::Break(123));
255        assert!(matches!(h.dispatch(crate::deps![State::Other]).await, ControlFlow::Continue(_)));
256    }
257}