Skip to main content

standout_dispatch/
lib.rs

1//! Command dispatch and orchestration for clap-based CLIs.
2//!
3//! `standout-dispatch` provides command routing, handler execution, and a hook
4//! system for CLI applications. It orchestrates the execution flow while remaining
5//! agnostic to rendering implementation.
6//!
7//! # Architecture
8//!
9//! Dispatch is an orchestration layer that manages this execution flow:
10//!
11//! ```text
12//! parsed CLI args
13//!   → pre-dispatch hook (validation, setup)
14//!   → logic handler (business logic → serializable data)
15//!   → post-dispatch hook (data transformation)
16//!   → render handler (view + data → string output)
17//!   → post-output hook (output transformation)
18//! ```
19//!
20//! ## Design Rationale
21//!
22//! Dispatch deliberately does not own rendering or output format logic:
23//!
24//! - Logic handlers have a strict input signature (`&ArgMatches`, `&CommandContext`)
25//!   and return serializable data. They focus purely on business logic.
26//!
27//! - Render handlers are pluggable callbacks provided by the consuming framework.
28//!   They receive (view name, data) and return a formatted string. All rendering
29//!   decisions (format, theme, template engine) live in the render handler.
30//!
31//! This separation allows:
32//! - Using dispatch without any rendering (just return data)
33//! - Using dispatch with custom renderers (not just standout-render)
34//! - Keeping format/theme/template logic out of the dispatch layer
35//!
36//! ## Render Handler Pattern
37//!
38//! The render handler is a closure that captures rendering context:
39//!
40//! ```rust,ignore
41//! // Framework (e.g., standout) creates the render handler at runtime
42//! // after parsing CLI args to determine format
43//! let format = extract_output_mode(&matches);  // --output=json
44//! let theme = &config.theme;
45//!
46//! let render_handler = move |view: &str, data: &Value| {
47//!     // All format/theme knowledge lives here, not in dispatch
48//!     my_renderer::render(view, data, theme, format)
49//! };
50//!
51//! dispatcher.run_with_renderer(matches, render_handler);
52//! ```
53//!
54//! This pattern means dispatch calls `render_handler(view, data)` without knowing
55//! what format, theme, or template engine is being used.
56//!
57//! # State Management
58//!
59//! [`CommandContext`] provides two mechanisms for dependency injection:
60//!
61//! - **`app_state`**: Immutable, app-lifetime state (database, config, API clients).
62//!   Configured at app build time, shared across all dispatches via `Arc<Extensions>`.
63//!
64//! - **`extensions`**: Mutable, per-request state. Injected by pre-dispatch hooks
65//!   for request-scoped data like user sessions or request IDs.
66//!
67//! ```rust,ignore
68//! // App-level state (build time)
69//! App::builder()
70//!     .app_state(Database::connect()?)
71//!     .app_state(Config::load()?)
72//!
73//! // In handler
74//! fn handler(matches: &ArgMatches, ctx: &CommandContext) -> HandlerResult<T> {
75//!     let db = ctx.app_state.get_required::<Database>()?;   // shared
76//!     let scope = ctx.extensions.get_required::<UserScope>()?; // per-request
77//!     // ...
78//! }
79//! ```
80//!
81//! # Features
82//!
83//! - Command routing: Extract command paths from clap `ArgMatches`
84//! - Handler traits: Thread-safe ([`Handler`]) and local ([`LocalHandler`]) variants
85//! - Hook system: Pre/post dispatch and post-output hooks for cross-cutting concerns
86//! - State injection: App-level state via `app_state`, per-request state via `extensions`
87//! - Render abstraction: Pluggable render handlers via [`RenderFn`] / [`LocalRenderFn`]
88//!
89//! # Usage
90//!
91//! ## Standalone (no rendering framework)
92//!
93//! ```rust,ignore
94//! use standout_dispatch::{Handler, Output, from_fn};
95//!
96//! // Simple render handler that just serializes to JSON
97//! let render = from_fn(|data, _| Ok(serde_json::to_string_pretty(data)?));
98//!
99//! Dispatcher::builder()
100//!     .command("list", list_handler, render)
101//!     .build()?
102//!     .run(cmd, args);
103//! ```
104//!
105//! ## With standout framework
106//!
107//! The `standout` crate provides full integration with templates and themes:
108//!
109//! ```rust,ignore
110//! use standout::{App, embed_templates};
111//!
112//! App::builder()
113//!     .templates(embed_templates!("src/templates"))
114//!     .command("list", list_handler, "list")  // template name
115//!     .build()?
116//!     .run(cmd, args);
117//! ```
118//!
119//! In this case, `standout` creates the render handler internally, injecting
120//! the template registry, theme, and output format from CLI args.
121
122// Core modules
123mod dispatch;
124mod handler;
125mod hooks;
126mod render;
127
128// Re-export command routing utilities
129pub use dispatch::{
130    extract_command_path, get_deepest_matches, has_subcommand, insert_default_command,
131    path_to_string, string_to_path,
132};
133
134// Re-export handler types
135pub use handler::{
136    CommandContext, Extensions, FnHandler, Handler, HandlerResult, IntoHandlerResult,
137    LocalFnHandler, LocalHandler, LocalSimpleFnHandler, Output, RunResult, SimpleFnHandler,
138};
139
140// Re-export hook types
141pub use hooks::{
142    HookError, HookPhase, Hooks, PostDispatchFn, PostOutputFn, PreDispatchFn, RenderedOutput,
143};
144
145// Re-export render abstraction
146pub use render::{from_fn, from_fn_mut, LocalRenderFn, RenderError, RenderFn};