pub trait AsyncCommandHandler: Send + Sync {
// Required method
fn execute<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
context: &'life1 mut dyn ExecutionContext,
args: &'life2 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
// Provided method
fn validate<'life0, 'life1, 'async_trait>(
&'life0 self,
_args: &'life1 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
}Expand description
Async counterpart of CommandHandler.
Additive to CommandHandler (see DD-022) — it does not replace it.
Implementations use this trait when their command body needs to perform
async I/O (network calls, streaming, etc.). The signatures deliberately
mirror CommandHandler exactly, execute/validate aside from the
async keyword, so that migrating a handler from sync to async is a
mechanical change.
§Object Safety
Made dyn-compatible via #[async_trait] (which desugars async fn to
a boxed, pinned future under the hood). Stored as Box<dyn AsyncCommandHandler> in the registry, exactly like CommandHandler is
stored as Box<dyn CommandHandler>.
§Thread Safety
Same constraint as CommandHandler: Send + Sync is required so the
handler can be shared across the registry and, transitively, across
threads if the application needs it.
§Why a separate trait instead of an async CommandHandler?
Existing sync CommandHandler implementations (including downstream
consumers) must keep compiling unchanged. See DD-022 for the full
rationale, including why tokio is not a dependency of dynamic-cli
itself and why driving the returned future via
futures::executor::block_on at the dispatch site is safe.
§Example
use std::collections::HashMap;
use async_trait::async_trait;
use dynamic_cli::executor::AsyncCommandHandler;
use dynamic_cli::context::ExecutionContext;
use dynamic_cli::error::ExecutionError;
use dynamic_cli::Result;
struct FetchCommand;
#[async_trait]
impl AsyncCommandHandler for FetchCommand {
async fn execute(
&self,
_context: &mut dyn ExecutionContext,
args: &HashMap<String, String>,
) -> Result<()> {
let url = args.get("url").ok_or_else(|| {
ExecutionError::CommandFailed(anyhow::anyhow!("Missing 'url' argument"))
})?;
// Real implementations would `.await` an async HTTP call here.
println!("Fetching {url}...");
Ok(())
}
async fn validate(&self, args: &HashMap<String, String>) -> Result<()> {
if !args.contains_key("url") {
return Err(ExecutionError::CommandFailed(anyhow::anyhow!("url is required")).into());
}
Ok(())
}
}Required Methods§
Sourcefn execute<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
context: &'life1 mut dyn ExecutionContext,
args: &'life2 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn execute<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
context: &'life1 mut dyn ExecutionContext,
args: &'life2 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Async equivalent of CommandHandler::execute. Same contract:
receives the mutable execution context and the parsed arguments,
returns Ok(()) on success or a DynamicCliError on failure.
Provided Methods§
Sourcefn validate<'life0, 'life1, 'async_trait>(
&'life0 self,
_args: &'life1 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn validate<'life0, 'life1, 'async_trait>(
&'life0 self,
_args: &'life1 HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Async equivalent of CommandHandler::validate. Same contract and
same default (accepts all arguments) — override only for custom
validation logic.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".