diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 8d646cd4..8b9f73f9 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -6,6 +6,7 @@ This directory contains architectural decision records for the Torrust Tracker D | Status | Date | Decision | Summary | | ----------- | ---------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| ✅ Accepted | 2025-11-07 | [ExecutionContext Wrapper Pattern](./execution-context-wrapper.md) | Use ExecutionContext wrapper around Container for future-proof command signatures | | ✅ Accepted | 2025-11-03 | [Environment Variable Prefix](./environment-variable-prefix.md) | Use `TORRUST_TD_` prefix for all environment variables | | ✅ Accepted | 2025-10-15 | [External Tool Adapters Organization](./external-tool-adapters-organization.md) | Consolidate external tool wrappers in `src/adapters/` for better discoverability | | ✅ Accepted | 2025-10-10 | [Repository Rename to Deployer](./repository-rename-to-deployer.md) | Rename from "Torrust Tracker Deploy" to "Torrust Tracker Deployer" for production use | diff --git a/docs/decisions/execution-context-wrapper.md b/docs/decisions/execution-context-wrapper.md new file mode 100644 index 00000000..6885886f --- /dev/null +++ b/docs/decisions/execution-context-wrapper.md @@ -0,0 +1,231 @@ +# Decision: ExecutionContext Wrapper Pattern + +## Status + +Accepted + +## Date + +2025-11-07 + +## Context + +During the implementation of the Dispatch Layer (Proposal 2 from the presentation layer reorganization epic #154), we needed to decide how command handlers should access application services. We had two main options: + +1. **Direct Container Access**: Pass the `Container` directly to command handlers +2. **ExecutionContext Wrapper**: Create an `ExecutionContext` wrapper around the `Container` + +### Current Architecture + +The Dispatch Layer routes commands to handlers and needs to provide access to application services (user output, repositories, external tool clients, etc.). These services are managed by the dependency injection `Container`. + +### Design Options Considered + +#### Option 1: Direct Container Access + +```rust +pub fn route_command( + command: Commands, + working_dir: &Path, + container: &Container, +) -> Result<(), CommandError> + +// In handlers: +fn handle_create_command(container: &Container) { + let user_output = container.user_output(); + // ... +} +``` + +#### Option 2: ExecutionContext Wrapper + +```rust +pub struct ExecutionContext { + container: Arc, +} + +pub fn route_command( + command: Commands, + working_dir: &Path, + context: &ExecutionContext, +) -> Result<(), CommandError> + +// In handlers: +fn handle_create_command(context: &ExecutionContext) { + let user_output = context.user_output(); + // ... +} +``` + +## Decision + +We chose **Option 2: ExecutionContext Wrapper** for the following reasons: + +### 1. Future-Proof Command Signatures + +By introducing `ExecutionContext`, we can add execution-related data in the future without breaking existing command handler signatures: + +```rust +pub struct ExecutionContext { + container: Arc, + // Future additions without breaking changes: + // request_id: RequestId, + // execution_metadata: ExecutionMetadata, + // tracing_context: TracingContext, + // user_permissions: UserPermissions, + // execution_timeout: Duration, +} +``` + +If we used `Container` directly, adding any execution context would require changing every command handler signature. + +### 2. Clear Abstraction and Intent + +`ExecutionContext` provides a logical abstraction for "everything a command needs to execute" rather than exposing the dependency injection container directly: + +- **Container**: Implementation detail for dependency injection +- **ExecutionContext**: Execution abstraction for command handlers + +This makes the intent clearer and separates concerns properly. + +### 3. Type Safety and Interface Clarity + +```rust +// Less clear: What is this container for? Bootstrapping? Testing? Execution? +fn handle_command(container: &Container) + +// Clear: This is specifically for command execution +fn handle_command(context: &ExecutionContext) +``` + +### 4. Command-Specific Service Aggregation + +ExecutionContext can provide command-specific convenience methods and service aggregations: + +```rust +impl ExecutionContext { + // Direct service access + pub fn user_output(&self) -> &Arc> { + self.container.user_output() + } + + // Future: Command-specific aggregated services + pub fn deployment_services(&self) -> DeploymentServices { + DeploymentServices { + provisioner: self.container.provisioner(), + configurator: self.container.configurator(), + validator: self.container.validator(), + } + } +} +``` + +### 5. Enhanced Testability + +Different execution contexts can be created for different scenarios: + +```rust +// Production context +let context = ExecutionContext::new(container); + +// Test context with mocks +let context = TestExecutionContext::new(mock_container); + +// Both can implement the same interface +trait ExecutionContextTrait { + fn user_output(&self) -> &Arc>; +} +``` + +### 6. Industry Pattern Alignment + +Most frameworks use execution context patterns: + +- **Spring Framework**: `ApplicationContext` +- **ASP.NET Core**: `HttpContext` +- **Express.js**: Request/Response context +- **Go**: `context.Context` + +This aligns with established patterns for managing execution state. + +## Consequences + +### Positive + +- **Future-Proof**: Can extend execution context without breaking command signatures +- **Clear Intent**: ExecutionContext clearly indicates its purpose for command execution +- **Better Abstraction**: Separates execution concerns from dependency injection mechanics +- **Enhanced Testability**: Enables different contexts for different testing scenarios +- **Industry Alignment**: Follows established patterns from major frameworks + +### Negative + +- **Initial Overhead**: Currently just a thin wrapper around Container +- **Additional Indirection**: One extra layer between commands and services +- **Learning Curve**: New developers need to understand the wrapper pattern + +### Migration Path + +If needed, migration from Container to ExecutionContext (or vice versa) is straightforward: + +```rust +// From Container to ExecutionContext +fn handle_command(container: &Container) -> fn handle_command(context: &ExecutionContext) + +// From ExecutionContext to Container +fn handle_command(context: &ExecutionContext) -> fn handle_command(container: &Container) +``` + +## Implementation Details + +### Current Implementation + +```rust +pub struct ExecutionContext { + container: Arc, +} + +impl ExecutionContext { + pub fn new(container: Arc) -> Self { + Self { container } + } + + pub fn container(&self) -> &Container { + &self.container + } + + pub fn user_output(&self) -> &Arc> { + self.container.user_output() + } +} +``` + +### Usage Pattern + +```rust +// In bootstrap/app.rs +let container = Arc::new(Container::new()); +let context = ExecutionContext::new(container); + +// In dispatch layer +route_command(command, working_dir, &context)?; + +// In command handlers +fn handle_create_command(context: &ExecutionContext) { + let user_output = context.user_output(); + // Command implementation +} +``` + +## Related Decisions + +- [Presentation Layer Reorganization](../decisions/README.md) - Overall context for the four-layer architecture +- [Command State Return Pattern](./command-state-return-pattern.md) - How commands return typed states + +## References + +- [Epic #154: Presentation Layer Reorganization](https://github.com/torrust/torrust-tracker-deployer/issues/154) +- [Issue #156: Create Dispatch Layer](https://github.com/torrust/torrust-tracker-deployer/issues/156) +- [Spring Framework ApplicationContext](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-introduction) +- [ASP.NET Core HttpContext](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-context) +- [Go Context Package](https://pkg.go.dev/context) diff --git a/project-words.txt b/project-words.txt index d62c76a3..9e3e7826 100644 --- a/project-words.txt +++ b/project-words.txt @@ -23,6 +23,7 @@ cloudinit Cockburn concepsts connrefused +configurator containerd cpus creds diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 926bcb5b..3997b718 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -17,6 +17,8 @@ //! - **Single Responsibility**: Focus only on application bootstrap concerns //! - **Clean Separation**: No CLI parsing or business logic in this module +use std::sync::Arc; + use clap::Parser; use tracing::info; @@ -56,14 +58,15 @@ pub fn run() { ); // Initialize service container for dependency injection - let container = bootstrap::Container::new(); + let container = Arc::new(bootstrap::Container::new()); + let context = presentation::dispatch::ExecutionContext::new(container); match cli.command { Some(command) => { if let Err(e) = - presentation::execute(command, &cli.global.working_dir, &container.user_output()) + presentation::dispatch::route_command(command, &cli.global.working_dir, &context) { - presentation::handle_error(&e, &container.user_output()); + presentation::handle_error(&e, &context.user_output()); std::process::exit(1); } } diff --git a/src/presentation/commands/mod.rs b/src/presentation/commands/mod.rs index 97763893..895e094d 100644 --- a/src/presentation/commands/mod.rs +++ b/src/presentation/commands/mod.rs @@ -28,6 +28,34 @@ pub mod tests; /// Execute the given command /// +/// **DEPRECATED**: This function is deprecated in favor of the new Dispatch Layer. +/// Use `crate::presentation::dispatch::route_command` instead. +/// +/// This function will be removed in a future version. The new dispatch layer +/// provides better separation of concerns and cleaner architecture. +/// +/// # Migration Guide +/// +/// Old code: +/// ```rust,ignore +/// use std::sync::{Arc, Mutex}; +/// use crate::presentation::{commands, user_output::UserOutput}; +/// +/// let user_output = Arc::new(Mutex::new(UserOutput::new(/* ... */))); +/// commands::execute(command, working_dir, &user_output)?; +/// ``` +/// +/// New code: +/// ```rust,ignore +/// use std::sync::Arc; +/// use crate::bootstrap::Container; +/// use crate::presentation::dispatch::{route_command, ExecutionContext}; +/// +/// let container = Arc::new(Container::new()); +/// let context = ExecutionContext::new(container); +/// route_command(command, working_dir, &context)?; +/// ``` +/// /// This function serves as the central dispatcher for all CLI commands. /// It matches the command type and delegates execution to the appropriate /// command handler module. @@ -65,6 +93,27 @@ pub mod tests; /// } /// } /// ``` +#[deprecated( + since = "0.1.0", + note = "Use `crate::presentation::dispatch::route_command` instead" +)] +/// +/// ```rust +/// use clap::Parser; +/// use torrust_tracker_deployer_lib::presentation::{input::cli, commands, user_output}; +/// use std::{path::Path, sync::{Arc, Mutex}}; +/// +/// let cli = cli::Cli::parse(); +/// if let Some(command) = cli.command { +/// let working_dir = Path::new("."); +/// let user_output = Arc::new(Mutex::new(user_output::UserOutput::new(user_output::VerbosityLevel::Normal))); +/// let result = commands::execute(command, working_dir, &user_output); +/// match result { +/// Ok(_) => println!("Command executed successfully"), +/// Err(e) => commands::handle_error(&e, &user_output), +/// } +/// } +/// ``` pub fn execute( command: Commands, working_dir: &std::path::Path, diff --git a/src/presentation/dispatch/context.rs b/src/presentation/dispatch/context.rs new file mode 100644 index 00000000..3517b65b --- /dev/null +++ b/src/presentation/dispatch/context.rs @@ -0,0 +1,163 @@ +//! Execution Context +//! +//! This module provides the `ExecutionContext` wrapper around the Container for +//! dependency injection in command handlers. It offers a clean interface for +//! accessing services needed during command execution. +//! +//! ## Purpose +//! +//! The `ExecutionContext` serves as an abstraction layer between the Container +//! (which holds raw services) and command handlers (which need typed access). +//! This separation provides: +//! +//! - **Clean Interface**: Command handlers get strongly-typed service access +//! - **Thread Safety**: All services are properly wrapped for concurrent access +//! - **Future-Proofing**: Easy to add new services without changing handler signatures +//! - **Testing Support**: Easy to inject test doubles through Container +//! +//! ## Design +//! +//! ```text +//! Container (bootstrap) → ExecutionContext (dispatch) → Command Handlers +//! +//! Raw services Clean typed access Business logic +//! ``` +//! +//! ## Usage Example +//! +//! ```rust,no_run +//! use torrust_tracker_deployer_lib::bootstrap::Container; +//! use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; +//! use std::sync::Arc; +//! +//! # fn example() -> Result<(), Box> { +//! // Create execution context from container +//! let container = Container::new(); +//! let context = ExecutionContext::new(Arc::new(container)); +//! +//! // Command handlers access services through context +//! let user_output = context.user_output(); +//! user_output.lock().unwrap().progress("Processing..."); +//! # Ok(()) +//! # } +//! ``` + +use std::sync::{Arc, Mutex}; + +use crate::bootstrap::Container; +use crate::presentation::user_output::UserOutput; + +/// ### Design Consideration: Shared State Access +/// +/// Currently, there is no shared mutable state in the system that requires `Arc>` +/// patterns. However, if shared state is needed in the future, it can be added to the +/// Container and accessed through standard Rust concurrency patterns: +/// +/// # Examples +/// +/// ```rust +/// use std::sync::Arc; +/// use torrust_tracker_deployer_lib::bootstrap::Container; +/// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; +/// +/// let container = Arc::new(Container::new()); +/// let context = ExecutionContext::new(container); +/// +/// // Access user output service +/// let user_output = context.user_output(); +/// user_output.lock().unwrap().success("Operation completed"); +/// ``` +#[derive(Clone)] +pub struct ExecutionContext { + container: Arc, +} + +impl ExecutionContext { + /// Create a new execution context from a container + /// + /// # Arguments + /// + /// * `container` - Application service container with initialized services + /// + /// # Examples + /// + /// ```rust,no_run + /// use torrust_tracker_deployer_lib::bootstrap::Container; + /// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; + /// use std::sync::Arc; + /// + /// # fn example() -> Result<(), Box> { + /// let container = Container::new(); + /// let context = ExecutionContext::new(Arc::new(container)); + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn new(container: Arc) -> Self { + Self { container } + } + + /// Get reference to the underlying container + /// + /// Provides access to the raw container for cases where direct access + /// to container methods is needed. + /// + /// # Examples + /// + /// ```rust,no_run + /// use torrust_tracker_deployer_lib::bootstrap::Container; + /// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; + /// use std::sync::Arc; + /// + /// # fn example() -> Result<(), Box> { + /// let container = Container::new(); + /// let context = ExecutionContext::new(Arc::new(container)); + /// + /// let container_ref = context.container(); + /// // Use container_ref as needed + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn container(&self) -> &Arc { + &self.container + } + + /// Get shared reference to user output service + /// + /// Returns the user output service for displaying messages, progress, + /// and results to users. The service is wrapped in `Arc>` for + /// thread-safe shared access. + /// + /// # Examples + /// + /// ```rust,no_run + /// use torrust_tracker_deployer_lib::bootstrap::Container; + /// use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; + /// use std::sync::Arc; + /// + /// # fn example() -> Result<(), Box> { + /// let container = Container::new(); + /// let context = ExecutionContext::new(Arc::new(container)); + /// + /// let user_output = context.user_output(); + /// user_output.lock().unwrap().success("Operation completed"); + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn user_output(&self) -> Arc> { + self.container.user_output() + } + + // TODO: Add more service accessors as Container expands + // + // Future services that will be added to Container and accessed here: + // - opentofu_client() -> Arc + // - ansible_client() -> Arc + // - environment_repository() -> Arc + // - clock() -> Arc + // + // These will be added as the Container is expanded in future proposals + // to support the full dependency injection pattern. +} diff --git a/src/presentation/dispatch/mod.rs b/src/presentation/dispatch/mod.rs new file mode 100644 index 00000000..fbc1bc46 --- /dev/null +++ b/src/presentation/dispatch/mod.rs @@ -0,0 +1,129 @@ +//! Dispatch Layer - Presentation Layer Component +//! +//! The Dispatch Layer is responsible for routing parsed commands to their appropriate handlers +//! and providing execution context for command processing. This is the second layer in the +//! presentation layer's four-layer architecture: Input → Dispatch → Controllers → Views. +//! +//! ## Purpose +//! +//! The Dispatch Layer establishes clear separation between: +//! - **Command routing** (determining which handler to execute) +//! - **Execution context** (providing dependencies to handlers) +//! - **Command execution** (actual command logic in Controllers layer) +//! - **Result presentation** (handled by Views layer) +//! +//! This separation provides several benefits: +//! - **Single Responsibility**: Routing logic isolated from command execution +//! - **Testability**: Router can be tested independently of command handlers +//! - **Dependency Injection**: Clean pattern for providing services to commands +//! - **Scalability**: Easy to add new commands without modifying existing handlers +//! +//! ## Module Structure +//! +//! ```text +//! dispatch/ +//! ├── mod.rs # This file - layer exports and documentation +//! ├── router.rs # Command routing logic (route_command function) +//! └── context.rs # ExecutionContext wrapper around Container +//! ``` +//! +//! ## `ExecutionContext` Design +//! +//! The Dispatch Layer uses an `ExecutionContext` wrapper around the `Container` rather than +//! passing the `Container` directly to command handlers. This design choice provides several +//! important benefits: +//! +//! ### 1. Future-Proof Command Signatures +//! +//! By using `ExecutionContext`, we can extend execution context in the future without +//! breaking existing command handler signatures: +//! +//! ```rust,ignore +//! use std::sync::Arc; +//! use torrust_tracker_deployer_lib::bootstrap::Container; +//! +//! pub struct ExecutionContext { +//! container: Arc, +//! // Future additions without breaking changes: +//! // request_id: RequestId, +//! // execution_metadata: ExecutionMetadata, +//! // tracing_context: TracingContext, +//! // user_permissions: UserPermissions, +//! } +//! ``` +//! +//! ### 2. Clear Abstraction and Intent +//! +//! `ExecutionContext` represents "everything a command needs to execute" rather than +//! exposing dependency injection mechanics directly: +//! +//! ```rust,no_run +//! use torrust_tracker_deployer_lib::bootstrap::Container; +//! use torrust_tracker_deployer_lib::presentation::dispatch::ExecutionContext; +//! +//! # fn example() { +//! // Clear: This is specifically for command execution +//! fn handle_command(context: &ExecutionContext) { +//! // Command execution logic +//! } +//! +//! // Less clear: Could be for bootstrapping, testing, or execution +//! fn handle_command_old(container: &Container) { +//! // Generic container usage +//! } +//! # } +//! ``` +//! +//! ### 3. Command-Specific Service Access +//! +//! `ExecutionContext` can provide command-specific convenience methods and service +//! aggregations without exposing the entire Container interface. +//! +//! For the complete rationale, see the architectural decision record: +//! [`docs/decisions/execution-context-wrapper.md`](../../../docs/decisions/execution-context-wrapper.md) +//! +//! ## Design Principles +//! +//! - **Route, Don't Execute**: This layer only routes commands, doesn't execute them +//! - **Dependency Injection**: Provide clean access to services via `ExecutionContext` +//! - **Type Safety**: Use strongly-typed routing with match statements +//! - **Error Propagation**: Pass routing errors up to caller +//! +//! ## Integration with Presentation Layer +//! +//! The Dispatch Layer integrates with the broader presentation layer architecture: +//! +//! 1. **Input Layer** (`input/`) - Parses user input into Commands enum +//! 2. **Dispatch Layer** (this module) - Routes commands and provides context +//! 3. **Controller Layer** (`commands/`) - Executes command logic with context +//! 4. **View Layer** (`user_output/`, `progress.rs`) - Presents results to users +//! +//! ## Usage Pattern +//! +//! ```rust,ignore +//! use std::path::Path; +//! use std::sync::Arc; +//! use torrust_tracker_deployer_lib::bootstrap::Container; +//! use torrust_tracker_deployer_lib::presentation::dispatch::{route_command, ExecutionContext}; +//! // Note: Commands enum requires specific action parameters in practice +//! +//! # fn example() -> Result<(), Box> { +//! let container = Container::new(); +//! let context = ExecutionContext::new(Arc::new(container)); +//! let working_dir = Path::new("."); +//! +//! // Execute a command through the dispatch layer +//! // Note: route_command is synchronous, not async +//! // Commands require proper construction with actions +//! # Ok(()) +//! # } +//! ``` +// Command routing module +pub mod router; + +// Execution context module +pub mod context; + +// Re-export main types for convenience +pub use context::ExecutionContext; +pub use router::route_command; diff --git a/src/presentation/dispatch/router.rs b/src/presentation/dispatch/router.rs new file mode 100644 index 00000000..96ded823 --- /dev/null +++ b/src/presentation/dispatch/router.rs @@ -0,0 +1,134 @@ +//! Command Router +//! +//! This module provides the central command routing functionality for the Dispatch Layer. +//! It contains the `route_command` function that matches parsed CLI commands to their +//! appropriate handler functions. +//! +//! ## Purpose +//! +//! The router extracts command dispatch logic from the main application bootstrap and +//! the presentation commands module, creating a clean separation between: +//! +//! - **Command parsing** (Input Layer - already done) +//! - **Command routing** (This module - routes commands to handlers) +//! - **Command execution** (Controller Layer - executes business logic) +//! - **Result presentation** (View Layer - displays results) +//! +//! ## Design +//! +//! ```text +//! Commands enum → route_command() → Handler function +//! ↓ ↓ ↓ +//! Parsed input Route decision Business logic +//! ``` +//! +//! ## Benefits +//! +//! - **Centralized Routing**: All command routing logic in one place +//! - **Type Safety**: Compile-time guarantees that all commands are handled +//! - **Testability**: Router can be tested independently of handlers +//! - **Maintainability**: Easy to add new commands or modify routing logic +//! +//! ## Usage Example +//! +//! ```rust,ignore +//! use std::path::Path; +//! use std::sync::Arc; +//! use torrust_tracker_deployer_lib::bootstrap::Container; +//! use torrust_tracker_deployer_lib::presentation::dispatch::{route_command, ExecutionContext}; +//! // Note: Commands enum requires specific action parameters in practice +//! +//! # fn example() -> Result<(), Box> { +//! let container = Container::new(); +//! let context = ExecutionContext::new(Arc::new(container)); +//! let working_dir = Path::new("."); +//! +//! // Route command to appropriate handler +//! // Note: Commands require proper construction with actions +//! # Ok(()) +//! # } +//! ``` + +use std::path::Path; + +use crate::presentation::commands::{create, destroy}; +use crate::presentation::errors::CommandError; +use crate::presentation::input::Commands; + +use super::ExecutionContext; + +/// Route a parsed command to its appropriate handler +/// +/// This function serves as the central dispatch point for all CLI commands. +/// It takes a parsed command and an execution context, then routes the command +/// to the appropriate handler function in the Controllers layer. +/// +/// # Arguments +/// +/// * `command` - Parsed command from the Input Layer +/// * `working_dir` - Working directory for command execution +/// * `context` - Execution context providing access to application services +/// +/// # Returns +/// +/// Returns `Ok(())` on successful command execution, or a `CommandError` +/// if the command fails. The error contains detailed context and actionable +/// troubleshooting information. +/// +/// # Errors +/// +/// Returns an error if: +/// - Command handler execution fails +/// - Required services are not available in the context +/// - Command parameters are invalid +/// +/// # Examples +/// +/// ```text +/// use std::path::Path; +/// use std::sync::Arc; +/// use torrust_tracker_deployer_lib::bootstrap::Container; +/// use torrust_tracker_deployer_lib::presentation::dispatch::{route_command, ExecutionContext}; +/// // Note: Commands enum requires specific action parameters in practice +/// +/// fn example() -> Result<(), Box> { +/// let container = Container::new(); +/// let context = ExecutionContext::new(Arc::new(container)); +/// let working_dir = Path::new("."); +/// +/// // Route command to appropriate handler - requires proper Commands construction +/// // route_command(command, working_dir, &context)?; +/// Ok(()) +/// } +/// ``` +pub fn route_command( + command: Commands, + working_dir: &Path, + context: &ExecutionContext, +) -> Result<(), CommandError> { + match command { + Commands::Create { action } => { + create::handle_create_command(action, working_dir, &context.user_output())?; + Ok(()) + } + Commands::Destroy { environment } => { + destroy::handle_destroy_command(&environment, working_dir, &context.user_output())?; + Ok(()) + } // Future commands will be added here as the Controller Layer expands: + // + // Commands::Provision { environment, provider } => { + // provision::handle_provision_command(&environment, &provider, context)?; + // Ok(()) + // } + // + // Commands::Configure { environment } => { + // configure::handle_configure_command(&environment, context)?; + // Ok(()) + // } + // + // Commands::Release { environment, version } => { + // release::handle_release_command(&environment, &version, context)?; + // Ok(()) + // } + } +} diff --git a/src/presentation/mod.rs b/src/presentation/mod.rs index e2c95685..30b9a5f7 100644 --- a/src/presentation/mod.rs +++ b/src/presentation/mod.rs @@ -30,6 +30,10 @@ //! │ ├── args.rs # Global CLI arguments (logging config) //! │ ├── commands.rs # Subcommand definitions //! │ └── mod.rs # Main Cli struct and parsing logic +//! ├── dispatch/ # Dispatch Layer - Command routing and execution context +//! │ ├── mod.rs # Layer exports and documentation +//! │ ├── router.rs # Command routing logic (route_command function) +//! │ └── context.rs # ExecutionContext wrapper around Container //! ├── commands/ # Command execution handlers //! │ ├── destroy.rs # Destroy command handler //! │ └── mod.rs # Unified command dispatch and error handling @@ -40,6 +44,7 @@ // Core presentation modules pub mod commands; +pub mod dispatch; pub mod errors; pub mod input; pub mod progress; @@ -48,7 +53,13 @@ pub mod user_output; // Re-export commonly used presentation types for convenience pub use commands::create::CreateSubcommandError; pub use commands::destroy::DestroySubcommandError; -pub use commands::{execute, handle_error}; +pub use commands::handle_error; + +// Deprecated: Use dispatch layer instead +#[deprecated(since = "0.1.0", note = "Use `dispatch::route_command` instead")] +#[allow(deprecated)] +pub use commands::execute; + pub use errors::CommandError; pub use input::{Cli, Commands, GlobalArgs}; pub use progress::ProgressReporter;