Skip to main content

AsyncCommandHandler

Trait AsyncCommandHandler 

Source
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§

Source

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§

Source

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.

Source

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".

Implementors§