use std::future::Future;
use crate::action::ChatActionGuard;
use crate::context::Context;
#[cfg(not(target_arch = "wasm32"))]
pub trait FromContextBounds: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send> FromContextBounds for T {}
#[cfg(target_arch = "wasm32")]
pub trait FromContextBounds {}
#[cfg(target_arch = "wasm32")]
impl<T> FromContextBounds for T {}
#[cfg(not(target_arch = "wasm32"))]
pub trait ContextFutureBounds: Future + Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Future + Send + ?Sized> ContextFutureBounds for T {}
#[cfg(target_arch = "wasm32")]
pub trait ContextFutureBounds: Future {}
#[cfg(target_arch = "wasm32")]
impl<T: Future + ?Sized> ContextFutureBounds for T {}
pub trait FromContext: Sized + FromContextBounds {
fn from_context(ctx: &Context) -> impl ContextFutureBounds<Output = Self>;
}
impl FromContext for Context {
async fn from_context(ctx: &Context) -> Self {
ctx.clone()
}
}
#[derive(Debug, Clone)]
pub struct User {
pub id: String,
pub name: String,
}
impl FromContext for User {
async fn from_context(ctx: &Context) -> Self {
Self {
id: ctx.user_id().to_string(),
name: ctx.user_name().to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct Channel {
pub id: String,
}
impl FromContext for Channel {
async fn from_context(ctx: &Context) -> Self {
Self {
id: ctx.channel_id().to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct CommandName(pub String);
impl FromContext for CommandName {
async fn from_context(ctx: &Context) -> Self {
Self(ctx.command_name().unwrap_or_default().to_string())
}
}
#[derive(Debug, Clone)]
pub struct CommandArgs(pub String);
impl FromContext for CommandArgs {
async fn from_context(ctx: &Context) -> Self {
Self(ctx.command_args().unwrap_or_default().to_string())
}
}
#[derive(Debug, Clone)]
pub struct ButtonId(pub String);
impl FromContext for ButtonId {
async fn from_context(ctx: &Context) -> Self {
Self(ctx.button_id().unwrap_or_default().to_string())
}
}
#[derive(Debug, Clone)]
pub struct MessageContent(pub String);
impl FromContext for MessageContent {
async fn from_context(ctx: &Context) -> Self {
Self(ctx.message_content().unwrap_or_default().to_string())
}
}
macro_rules! impl_from_context_tuple {
() => {
impl FromContext for () {
async fn from_context(_ctx: &Context) -> Self {}
}
};
($($ty:ident),+) => {
impl<$($ty: FromContext,)+> FromContext for ($($ty,)+) {
async fn from_context(ctx: &Context) -> Self {
($($ty::from_context(ctx).await,)+)
}
}
};
}
impl_from_context_tuple!();
impl_from_context_tuple!(T1);
impl_from_context_tuple!(T1, T2);
impl_from_context_tuple!(T1, T2, T3);
impl_from_context_tuple!(T1, T2, T3, T4);
pub struct Typing(pub Option<ChatActionGuard>);
impl FromContext for Typing {
async fn from_context(ctx: &Context) -> Self {
Self(ctx.typing())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::{EmptyData, StubData};
use futures_lite::future::block_on;
fn stub() -> Context {
Context::new(StubData)
}
fn empty() -> Context {
Context::new(EmptyData)
}
#[test]
fn extracts_user_and_channel() {
let user = block_on(User::from_context(&stub()));
assert_eq!(
(user.id.as_str(), user.name.as_str()),
("stub-user", "stub-user")
);
assert_eq!(block_on(Channel::from_context(&stub())).id, "stub-channel");
}
#[test]
fn extracts_command_parts() {
assert_eq!(block_on(CommandName::from_context(&stub())).0, "cmd");
assert_eq!(block_on(CommandArgs::from_context(&stub())).0, "args");
assert_eq!(block_on(ButtonId::from_context(&stub())).0, "stub-button");
assert_eq!(
block_on(MessageContent::from_context(&stub())).0,
"stub message"
);
}
#[test]
fn absent_fields_extract_as_empty_strings() {
assert_eq!(block_on(CommandName::from_context(&empty())).0, "");
assert_eq!(block_on(CommandArgs::from_context(&empty())).0, "");
assert_eq!(block_on(ButtonId::from_context(&empty())).0, "");
assert_eq!(block_on(MessageContent::from_context(&empty())).0, "");
}
#[test]
fn tuples_extract_every_element() {
let (name, args, user) =
block_on(<(CommandName, CommandArgs, User)>::from_context(&stub()));
assert_eq!((name.0.as_str(), args.0.as_str()), ("cmd", "args"));
assert_eq!(user.id, "stub-user");
}
#[test]
fn context_extracts_itself() {
let ctx = block_on(Context::from_context(&stub()));
assert_eq!(ctx.user_id(), "stub-user");
}
#[test]
fn typing_is_absent_without_platform_support() {
assert!(block_on(Typing::from_context(&stub())).0.is_none());
}
}