pub trait AsyncCommandHandler: Send + Sync {
// Required method
fn execute<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
context: &'life1 mut dyn ExecutionContext,
args: &'life2 ParsedArgs,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait;
// Provided methods
fn validate<'life0, 'life1, 'async_trait>(
&'life0 self,
_args: &'life1 ParsedArgs,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn expected_fault_tolerance(&self) -> Option<bool> { ... }
}Expand description
Async counterpart of CommandHandler.
Additive to CommandHandler — 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.
dynamic-cli does not depend on tokio or any other async runtime.
The CLI and REPL dispatch loops are strictly sequential (each command
runs to completion before the next input is read), so the dispatch
site drives the returned future with futures::executor::block_on.
The future borrows the execution context, so it could not be spawned
onto a runtime anyway. A handler that needs a runtime, e.g. for
network I/O, uses its own inside execute.
§Example
use async_trait::async_trait;
use dynamic_cli::executor::{AsyncCommandHandler, ParsedArgs};
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: &ParsedArgs,
) -> Result<()> {
let url = args.get_scalar("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: &ParsedArgs) -> Result<()> {
if args.get_scalar("url").is_none() {
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 ParsedArgs,
) -> 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 ParsedArgs,
) -> 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 ParsedArgs,
) -> 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 ParsedArgs,
) -> 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.
Sourcefn expected_fault_tolerance(&self) -> Option<bool>
fn expected_fault_tolerance(&self) -> Option<bool>
Mirrors CommandHandler::expected_fault_tolerance exactly — same
contract, same default (None), checked by
CommandRegistry::register_async the same way register_sync checks
it for CommandHandler.
Deliberately not an async fn, unlike execute/validate
above: this method does no work of its own and is evaluated at
registration time, before any dispatch — sync or async — so making
it async would only force register_async (itself a synchronous
function) to block_on it for no benefit.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".