pub struct CommandContext {
pub command_path: Vec<String>,
pub app_state: Arc<Extensions>,
pub extensions: Extensions,
}Expand description
Context passed to command handlers.
Provides information about the execution environment plus two mechanisms for state injection:
app_state: Immutable, app-lifetime state (Database, Config, API clients)extensions: Mutable, per-request state (UserScope, RequestId)
Note that output format is deliberately not included here - format decisions are made by the render handler, not by logic handlers.
§App State (Immutable, Shared)
App state is configured at build time and shared across all dispatches:
use standout::cli::App;
struct Database { /* ... */ }
struct Config { api_url: String }
App::builder()
.app_state(Database::connect()?)
.app_state(Config { api_url: "https://api.example.com".into() })
.command("list", list_handler, "{{ items }}")
.build()?Handlers retrieve app state immutably:
fn list_handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<Vec<Item>> {
let db = ctx.app_state.get_required::<Database>()?;
let config = ctx.app_state.get_required::<Config>()?;
Ok(Output::Render(db.list_items(&config.api_url)?))
}§Shared Mutable State
Since app_state is shared via Arc, it is immutable by default. To share mutable state
(like counters or caches), use interior mutability primitives like RwLock, Mutex, or atomic types:
use std::sync::atomic::AtomicUsize;
struct Metrics { request_count: AtomicUsize }
// Builder
App::builder().app_state(Metrics { request_count: AtomicUsize::new(0) });
// Handler
let metrics = ctx.app_state.get_required::<Metrics>()?;
metrics.request_count.fetch_add(1, Ordering::Relaxed);§Extensions (Mutable, Per-Request)
Pre-dispatch hooks inject per-request state into extensions:
use standout_dispatch::{Hooks, HookError, CommandContext};
struct UserScope { user_id: String }
let hooks = Hooks::new()
.pre_dispatch(|matches, ctx| {
let user_id = matches.get_one::<String>("user").unwrap();
ctx.extensions.insert(UserScope { user_id: user_id.clone() });
Ok(())
});
// In handler:
fn my_handler(matches: &clap::ArgMatches, ctx: &CommandContext) -> anyhow::Result<()> {
let scope = ctx.extensions.get_required::<UserScope>()?;
// use scope.user_id...
Ok(())
}Fields§
§command_path: Vec<String>The command path being executed (e.g., [“config”, “get”])
app_state: Arc<Extensions>Immutable app-level state shared across all dispatches.
Configured via AppBuilder::app_state(). Contains long-lived resources
like database connections, configuration, and API clients.
Use get::<T>() or get_required::<T>() to retrieve values.
extensions: ExtensionsMutable per-request state container.
Pre-dispatch hooks can insert values that handlers retrieve. Each dispatch gets a fresh Extensions instance.