foukoapi 0.1.0-alpha.1

Cross-platform bot framework in Rust. Write your handlers once, run the same bot on Telegram and Discord with shared accounts, embeds, keyboards and SQLite storage.
Documentation
//! Commands and handler plumbing.

use crate::{Ctx, Result};
use futures::future::BoxFuture;
use std::{future::Future, sync::Arc};

/// Boxed, type-erased async handler.
///
/// Created with [`Handler::new`] or (more commonly) by passing a plain async
/// closure to [`Bot::command`](crate::Bot::command).
#[derive(Clone)]
pub struct Handler {
    inner: Arc<dyn Fn(Ctx) -> BoxFuture<'static, Result<()>> + Send + Sync + 'static>,
}

impl Handler {
    /// Wrap any async `Fn(Ctx) -> impl Future<Output = Result<()>>` into a
    /// [`Handler`].
    pub fn new<F, Fut>(f: F) -> Self
    where
        F: Fn(Ctx) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = Result<()>> + Send + 'static,
    {
        Self {
            inner: Arc::new(move |ctx| {
                let fut = f(ctx);
                Box::pin(fut)
            }),
        }
    }

    /// Run the handler with a given context.
    pub async fn call(&self, ctx: Ctx) -> Result<()> {
        (self.inner)(ctx).await
    }
}

/// A single command: its trigger (`/help`, `/roll`, ...) and its handler.
#[derive(Clone)]
pub struct Command {
    /// The command trigger as written in chat, including the leading `/`.
    pub name: String,
    /// Handler invoked when the command fires.
    pub handler: Handler,
}

impl Command {
    /// Convenience constructor.
    pub fn new(name: impl Into<String>, handler: Handler) -> Self {
        Self {
            name: name.into(),
            handler,
        }
    }
}

impl std::fmt::Debug for Command {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Command")
            .field("name", &self.name)
            .finish_non_exhaustive()
    }
}