use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use rust_tg_bot_raw::types::update::Update;
use super::base::{ContextCallback, Handler, HandlerCallback, HandlerResult, MatchResult};
use crate::context::CallbackContext;
use crate::filters::base::{self, Filter, FilterResult};
pub type FilterFn = Arc<dyn Fn(&Update) -> bool + Send + Sync>;
pub struct MessageHandler {
filter: Option<base::F>,
callback: HandlerCallback,
block: bool,
context_callback: Option<ContextCallback>,
}
impl MessageHandler {
pub fn new<Cb, Fut>(filter: base::F, callback: Cb) -> Self
where
Cb: Fn(Arc<Update>, CallbackContext) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), crate::application::HandlerError>> + Send + 'static,
{
let cb = Arc::new(callback);
let context_cb: ContextCallback = Arc::new(move |update, ctx| {
let fut = cb(update, ctx);
Box::pin(fut)
as Pin<
Box<dyn Future<Output = Result<(), crate::application::HandlerError>> + Send>,
>
});
let noop_callback: HandlerCallback =
Arc::new(|_update, _mr| Box::pin(async { HandlerResult::Continue }));
Self {
filter: Some(filter),
callback: noop_callback,
block: true,
context_callback: Some(context_cb),
}
}
pub fn with_options(filter: Option<base::F>, callback: HandlerCallback, block: bool) -> Self {
Self {
filter,
callback,
block,
context_callback: None,
}
}
pub fn from_fn(filter: Option<FilterFn>, callback: HandlerCallback, block: bool) -> Self {
let f = filter.map(|closure| base::F::new(ClosureFilter(closure)));
Self {
filter: f,
callback,
block,
context_callback: None,
}
}
}
struct ClosureFilter(Arc<dyn Fn(&Update) -> bool + Send + Sync>);
impl base::Filter for ClosureFilter {
fn check_update(&self, update: &base::Update) -> FilterResult {
if (self.0)(update) {
FilterResult::Match
} else {
FilterResult::NoMatch
}
}
fn name(&self) -> &str {
"ClosureFilter"
}
}
impl Handler for MessageHandler {
fn check_update(&self, update: &Update) -> Option<MatchResult> {
match &self.filter {
Some(f) => {
let result = f.check_update(update);
match result {
FilterResult::NoMatch => None,
FilterResult::Match => Some(MatchResult::Empty),
FilterResult::MatchWithData(data) => Some(MatchResult::Custom(Box::new(data))),
}
}
None => Some(MatchResult::Empty),
}
}
fn handle_update(
&self,
update: Arc<Update>,
match_result: MatchResult,
) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
(self.callback)(update, match_result)
}
fn block(&self) -> bool {
self.block
}
fn handle_update_with_context(
&self,
update: Arc<Update>,
match_result: MatchResult,
context: CallbackContext,
) -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
if let Some(ref cb) = self.context_callback {
let fut = cb(update, context);
Box::pin(async move {
match fut.await {
Ok(()) => HandlerResult::Continue,
Err(crate::application::HandlerError::HandlerStop { .. }) => {
HandlerResult::Stop
}
Err(crate::application::HandlerError::Other(e)) => HandlerResult::Error(e),
}
})
} else {
(self.callback)(update, match_result)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filters::base::{All, FnFilter, F};
fn noop_callback() -> HandlerCallback {
Arc::new(|_update, _mr| Box::pin(async { HandlerResult::Continue }))
}
fn empty_update() -> Update {
serde_json::from_str(r#"{"update_id": 1}"#).unwrap()
}
fn text_update() -> Update {
serde_json::from_value(serde_json::json!({
"update_id": 1,
"message": {
"message_id": 1,
"date": 0,
"chat": {"id": 1, "type": "private"},
"text": "hello"
}
}))
.unwrap()
}
#[test]
fn no_filter_matches_everything() {
let h = MessageHandler::with_options(None, noop_callback(), true);
assert!(h.check_update(&empty_update()).is_some());
}
#[test]
fn filter_trait_all_matches_message() {
let h = MessageHandler::with_options(Some(F::new(All)), noop_callback(), true);
assert!(h.check_update(&text_update()).is_some());
}
#[test]
fn filter_trait_all_rejects_empty() {
let h = MessageHandler::with_options(Some(F::new(All)), noop_callback(), true);
assert!(h.check_update(&empty_update()).is_none());
}
#[test]
fn filter_not_combinator() {
let h = MessageHandler::with_options(Some(!F::new(All)), noop_callback(), true);
assert!(h.check_update(&text_update()).is_none());
assert!(h.check_update(&empty_update()).is_some());
}
#[test]
fn from_fn_filter_rejects() {
let h = MessageHandler::from_fn(Some(Arc::new(|_u| false)), noop_callback(), true);
assert!(h.check_update(&empty_update()).is_none());
}
#[test]
fn from_fn_filter_accepts() {
let h = MessageHandler::from_fn(Some(Arc::new(|_u| true)), noop_callback(), true);
assert!(h.check_update(&empty_update()).is_some());
}
#[test]
fn filter_data_flows_through() {
let f = FnFilter::new("always", |_| true);
let h = MessageHandler::with_options(Some(F::new(f)), noop_callback(), true);
let result = h.check_update(&empty_update());
assert!(result.is_some());
assert!(matches!(result.unwrap(), MatchResult::Empty));
}
#[test]
fn composed_filters_work() {
let always = FnFilter::new("always", |_| true);
let never = FnFilter::new("never", |_| false);
let h = MessageHandler::with_options(
Some(F::new(always) & F::new(never)),
noop_callback(),
true,
);
assert!(h.check_update(&empty_update()).is_none());
}
#[test]
fn or_composed_filters_work() {
let always = FnFilter::new("always", |_| true);
let never = FnFilter::new("never", |_| false);
let h = MessageHandler::with_options(
Some(F::new(always) | F::new(never)),
noop_callback(),
true,
);
assert!(h.check_update(&empty_update()).is_some());
}
}