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}