use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
type BoxFuture = Pin<Box<dyn Future<Output = Result<(), CommandError>> + Send>>;
type ErasedCommand<S> = Arc<dyn Fn(&S) -> BoxFuture + Send + Sync + 'static>;
pub trait Command: Send + Sync + 'static {
const NAME: &'static str;
}
#[derive(Debug)]
pub enum CommandError {
NotFound(String),
Failed(String),
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotFound(name) => write!(f, "command `{name}` is not registered"),
Self::Failed(msg) => write!(f, "command failed: {msg}"),
}
}
}
impl std::error::Error for CommandError {}
pub struct CommandRegistry<S: Send + Sync + 'static> {
handlers: Arc<HashMap<String, ErasedCommand<S>>>,
}
impl<S: Send + Sync + 'static> Default for CommandRegistry<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: Send + Sync + 'static> CommandRegistry<S> {
#[must_use]
pub fn new() -> Self {
Self {
handlers: Arc::new(HashMap::new()),
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn register<F, Fut>(self, name: &str, handler: F) -> Self
where
F: Fn(&S) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), CommandError>> + Send + 'static,
{
let handler = Arc::new(handler);
let erased: ErasedCommand<S> = Arc::new(move |state: &S| Box::pin(handler(state)));
let mut map = (*self.handlers).clone();
map.insert(name.to_string(), erased);
Self {
handlers: Arc::new(map),
}
}
#[must_use]
pub fn contains(&self, name: &str) -> bool {
self.handlers.contains_key(name)
}
#[must_use]
pub fn len(&self) -> usize {
self.handlers.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
#[must_use]
pub fn names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.handlers.keys().map(String::as_str).collect();
names.sort_unstable();
names
}
pub async fn run(&self, name: &str, state: &S) -> Result<(), CommandError> {
match self.handlers.get(name) {
Some(handler) => handler(state).await,
None => Err(CommandError::NotFound(name.to_string())),
}
}
}
impl<S: Send + Sync + 'static> std::fmt::Debug for CommandRegistry<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandRegistry")
.field("len", &self.handlers.len())
.field("names", &self.names())
.finish_non_exhaustive()
}
}
impl<S: Send + Sync + 'static> Clone for CommandRegistry<S> {
fn clone(&self) -> Self {
Self {
handlers: Arc::clone(&self.handlers),
}
}
}