rust_tg_bot_ext/handlers/
message.rs1use std::future::Future;
15use std::pin::Pin;
16use std::sync::Arc;
17
18use rust_tg_bot_raw::types::update::Update;
19
20use super::base::{ContextCallback, Handler, HandlerCallback, HandlerResult, MatchResult};
21use crate::context::CallbackContext;
22use crate::filters::base::{self, Filter, FilterResult};
23
24pub type FilterFn = Arc<dyn Fn(&Update) -> bool + Send + Sync>;
27
28pub struct MessageHandler {
70 filter: Option<base::F>,
71 callback: HandlerCallback,
72 block: bool,
73 context_callback: Option<ContextCallback>,
75}
76
77impl MessageHandler {
78 pub fn new<Cb, Fut>(filter: base::F, callback: Cb) -> Self
98 where
99 Cb: Fn(Arc<Update>, CallbackContext) -> Fut + Send + Sync + 'static,
100 Fut: Future<Output = Result<(), crate::application::HandlerError>> + Send + 'static,
101 {
102 let cb = Arc::new(callback);
103 let context_cb: ContextCallback = Arc::new(move |update, ctx| {
104 let fut = cb(update, ctx);
105 Box::pin(fut)
106 as Pin<
107 Box<dyn Future<Output = Result<(), crate::application::HandlerError>> + Send>,
108 >
109 });
110
111 let noop_callback: HandlerCallback =
113 Arc::new(|_update, _mr| Box::pin(async { HandlerResult::Continue }));
114
115 Self {
116 filter: Some(filter),
117 callback: noop_callback,
118 block: true,
119 context_callback: Some(context_cb),
120 }
121 }
122
123 pub fn with_options(filter: Option<base::F>, callback: HandlerCallback, block: bool) -> Self {
128 Self {
129 filter,
130 callback,
131 block,
132 context_callback: None,
133 }
134 }
135
136 pub fn from_fn(filter: Option<FilterFn>, callback: HandlerCallback, block: bool) -> Self {
141 let f = filter.map(|closure| base::F::new(ClosureFilter(closure)));
142 Self {
143 filter: f,
144 callback,
145 block,
146 context_callback: None,
147 }
148 }
149}
150
151struct ClosureFilter(Arc<dyn Fn(&Update) -> bool + Send + Sync>);
156
157impl base::Filter for ClosureFilter {
158 fn check_update(&self, update: &base::Update) -> FilterResult {
159 if (self.0)(update) {
160 FilterResult::Match
161 } else {
162 FilterResult::NoMatch
163 }
164 }
165
166 fn name(&self) -> &str {
167 "ClosureFilter"
168 }
169}
170
171impl Handler for MessageHandler {
172 fn check_update(&self, update: &Update) -> Option<MatchResult> {
173 match &self.filter {
174 Some(f) => {
175 let result = f.check_update(update);
176 match result {
177 FilterResult::NoMatch => None,
178 FilterResult::Match => Some(MatchResult::Empty),
179 FilterResult::MatchWithData(data) => Some(MatchResult::Custom(Box::new(data))),
180 }
181 }
182 None => Some(MatchResult::Empty),
183 }
184 }
185
186 fn handle_update(
187 &self,
188 update: Arc<Update>,
189 match_result: MatchResult,
190 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
191 (self.callback)(update, match_result)
192 }
193
194 fn block(&self) -> bool {
195 self.block
196 }
197
198 fn handle_update_with_context(
199 &self,
200 update: Arc<Update>,
201 match_result: MatchResult,
202 context: CallbackContext,
203 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
204 if let Some(ref cb) = self.context_callback {
205 let fut = cb(update, context);
206 Box::pin(async move {
207 match fut.await {
208 Ok(()) => HandlerResult::Continue,
209 Err(crate::application::HandlerError::HandlerStop { .. }) => {
210 HandlerResult::Stop
211 }
212 Err(crate::application::HandlerError::Other(e)) => HandlerResult::Error(e),
213 }
214 })
215 } else {
216 (self.callback)(update, match_result)
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::filters::base::{All, FnFilter, F};
225
226 fn noop_callback() -> HandlerCallback {
227 Arc::new(|_update, _mr| Box::pin(async { HandlerResult::Continue }))
228 }
229
230 fn empty_update() -> Update {
231 serde_json::from_str(r#"{"update_id": 1}"#).unwrap()
232 }
233
234 fn text_update() -> Update {
235 serde_json::from_value(serde_json::json!({
236 "update_id": 1,
237 "message": {
238 "message_id": 1,
239 "date": 0,
240 "chat": {"id": 1, "type": "private"},
241 "text": "hello"
242 }
243 }))
244 .unwrap()
245 }
246
247 #[test]
248 fn no_filter_matches_everything() {
249 let h = MessageHandler::with_options(None, noop_callback(), true);
250 assert!(h.check_update(&empty_update()).is_some());
251 }
252
253 #[test]
254 fn filter_trait_all_matches_message() {
255 let h = MessageHandler::with_options(Some(F::new(All)), noop_callback(), true);
256 assert!(h.check_update(&text_update()).is_some());
257 }
258
259 #[test]
260 fn filter_trait_all_rejects_empty() {
261 let h = MessageHandler::with_options(Some(F::new(All)), noop_callback(), true);
262 assert!(h.check_update(&empty_update()).is_none());
263 }
264
265 #[test]
266 fn filter_not_combinator() {
267 let h = MessageHandler::with_options(Some(!F::new(All)), noop_callback(), true);
269 assert!(h.check_update(&text_update()).is_none());
270 assert!(h.check_update(&empty_update()).is_some());
272 }
273
274 #[test]
275 fn from_fn_filter_rejects() {
276 let h = MessageHandler::from_fn(Some(Arc::new(|_u| false)), noop_callback(), true);
277 assert!(h.check_update(&empty_update()).is_none());
278 }
279
280 #[test]
281 fn from_fn_filter_accepts() {
282 let h = MessageHandler::from_fn(Some(Arc::new(|_u| true)), noop_callback(), true);
283 assert!(h.check_update(&empty_update()).is_some());
284 }
285
286 #[test]
287 fn filter_data_flows_through() {
288 let f = FnFilter::new("always", |_| true);
289 let h = MessageHandler::with_options(Some(F::new(f)), noop_callback(), true);
290 let result = h.check_update(&empty_update());
291 assert!(result.is_some());
292 assert!(matches!(result.unwrap(), MatchResult::Empty));
293 }
294
295 #[test]
296 fn composed_filters_work() {
297 let always = FnFilter::new("always", |_| true);
298 let never = FnFilter::new("never", |_| false);
299 let h = MessageHandler::with_options(
300 Some(F::new(always) & F::new(never)),
301 noop_callback(),
302 true,
303 );
304 assert!(h.check_update(&empty_update()).is_none());
305 }
306
307 #[test]
308 fn or_composed_filters_work() {
309 let always = FnFilter::new("always", |_| true);
310 let never = FnFilter::new("never", |_| false);
311 let h = MessageHandler::with_options(
312 Some(F::new(always) | F::new(never)),
313 noop_callback(),
314 true,
315 );
316 assert!(h.check_update(&empty_update()).is_some());
317 }
318}