Skip to main content

CommandContext

Struct CommandContext 

Source
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: Extensions

Mutable per-request state container.

Pre-dispatch hooks can insert values that handlers retrieve. Each dispatch gets a fresh Extensions instance.

Implementations§

Source§

impl CommandContext

Source

pub fn new(command_path: Vec<String>, app_state: Arc<Extensions>) -> Self

Creates a new CommandContext with the given path and shared app state.

This is more efficient than Default::default() when you already have app_state.

Trait Implementations§

Source§

impl Debug for CommandContext

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for CommandContext

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.