#[cfg(feature = "redis-storage")]
pub use crate::dispatching::dialogue::{RedisStorage, RedisStorageError};
#[cfg(feature = "sqlite-storage")]
pub use crate::dispatching::dialogue::{SqliteStorage, SqliteStorageError};
pub use get_chat_id::GetChatId;
pub use storage::*;
use teloxide_core::types::ChatId;
use std::{marker::PhantomData, sync::Arc};
mod get_chat_id;
mod storage;
#[derive(Debug)]
pub struct Dialogue<D, S>
where
S: ?Sized,
{
storage: Arc<S>,
chat_id: ChatId,
_phantom: PhantomData<D>,
}
impl<D, S> Clone for Dialogue<D, S>
where
S: ?Sized,
{
fn clone(&self) -> Self {
Dialogue { storage: self.storage.clone(), chat_id: self.chat_id, _phantom: PhantomData }
}
}
impl<D, S> Dialogue<D, S>
where
D: Send + 'static,
S: Storage<D> + ?Sized,
{
#[must_use]
pub fn new(storage: Arc<S>, chat_id: ChatId) -> Self {
Self { storage, chat_id, _phantom: PhantomData }
}
#[must_use]
pub fn chat_id(&self) -> ChatId {
self.chat_id
}
pub async fn get(&self) -> Result<Option<D>, S::Error> {
self.storage.clone().get_dialogue(self.chat_id).await
}
pub async fn get_or_default(&self) -> Result<D, S::Error>
where
D: Default,
{
match self.get().await? {
Some(d) => Ok(d),
None => {
self.storage.clone().update_dialogue(self.chat_id, D::default()).await?;
Ok(D::default())
}
}
}
pub async fn update<State>(&self, state: State) -> Result<(), S::Error>
where
D: From<State>,
{
let new_dialogue = state.into();
self.storage.clone().update_dialogue(self.chat_id, new_dialogue).await?;
Ok(())
}
pub async fn reset(&self) -> Result<(), S::Error>
where
D: Default,
{
self.update(D::default()).await
}
pub async fn exit(&self) -> Result<(), S::Error> {
self.storage.clone().remove_dialogue(self.chat_id).await
}
}
#[macro_export]
macro_rules! handler {
($($variant:ident)::+) => {
$crate::dptree::filter(|state| matches!(state, $($variant)::+))
};
($($variant:ident)::+ ($param:ident)) => {
$crate::dptree::filter_map(|state| match state {
$($variant)::+($param) => Some($param),
_ => None,
})
};
($($variant:ident)::+ ($($param:ident),+ $(,)?)) => {
$crate::dptree::filter_map(|state| match state {
$($variant)::+($($param),+) => Some(($($param),+ ,)),
_ => None,
})
};
($($variant:ident)::+ {$param:ident}) => {
$crate::dptree::filter_map(|state| match state {
$($variant)::+{$param} => Some($param),
_ => None,
})
};
($($variant:ident)::+ {$($param:ident),+ $(,)?}) => {
$crate::dptree::filter_map(|state| match state {
$($variant)::+ { $($param),+ } => Some(($($param),+ ,)),
_ => None,
})
};
}
#[cfg(test)]
mod tests {
use std::ops::ControlFlow;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum State {
A,
B(i32),
C(i32, &'static str),
D { foo: i32 },
E { foo: i32, bar: &'static str },
Other,
}
#[tokio::test]
async fn handler_empty_variant() {
let input = State::A;
let h: dptree::Handler<_, _> = handler![State::A].endpoint(|| async move { 123 });
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_single_fn_variant() {
let input = State::B(42);
let h: dptree::Handler<_, _> = handler![State::B(x)].endpoint(|x: i32| async move {
assert_eq!(x, 42);
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_single_fn_variant_trailing_comma() {
let input = State::B(42);
let h: dptree::Handler<_, _> = handler![State::B(x,)].endpoint(|(x,): (i32,)| async move {
assert_eq!(x, 42);
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_fn_variant() {
let input = State::C(42, "abc");
let h: dptree::Handler<_, _> =
handler![State::C(x, y)].endpoint(|(x, str): (i32, &'static str)| async move {
assert_eq!(x, 42);
assert_eq!(str, "abc");
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_single_struct_variant() {
let input = State::D { foo: 42 };
let h: dptree::Handler<_, _> = handler![State::D { foo }].endpoint(|x: i32| async move {
assert_eq!(x, 42);
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_single_struct_variant_trailing_comma() {
let input = State::D { foo: 42 };
#[rustfmt::skip] let h: dptree::Handler<_, _> = handler![State::D { foo, }].endpoint(|(x,): (i32,)| async move {
assert_eq!(x, 42);
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
#[tokio::test]
async fn handler_struct_variant() {
let input = State::E { foo: 42, bar: "abc" };
let h: dptree::Handler<_, _> =
handler![State::E { foo, bar }].endpoint(|(x, str): (i32, &'static str)| async move {
assert_eq!(x, 42);
assert_eq!(str, "abc");
123
});
assert_eq!(h.dispatch(dptree::deps![input]).await, ControlFlow::Break(123));
assert!(matches!(h.dispatch(dptree::deps![State::Other]).await, ControlFlow::Continue(_)));
}
}