Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions docs/contributing/module-organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,156 @@ These guidelines are general principles, not absolute rules. Consider deviating

Use your judgment, but **always prioritize readability and maintainability**.

## 📂 Command Module Structure Patterns

For presentation layer commands in `src/presentation/commands/`, we follow standardized folder structures that make it clear whether a command has subcommands or is a simple single-purpose command.

### Pattern 1: Simple Commands (No Subcommands)

For commands that perform a single operation (like `destroy`):

```text
src/presentation/commands/destroy/
├── mod.rs // Module documentation and re-exports
├── handler.rs // Main command implementation
├── errors.rs // Error types
└── tests/ // Test modules
├── mod.rs
└── integration.rs
```

**Key characteristics:**

- Uses `handler.rs` for the main command logic
- Direct implementation without routing
- Clean and focused on single responsibility

**Example:**

```rust
// In handler.rs
pub fn handle_destroy_command(
environment_name: &str,
working_dir: &Path,
) -> Result<(), DestroySubcommandError> {
// Direct implementation
}
```

### Pattern 2: Commands with Subcommands

For commands that route to multiple subcommands (like `create`):

```text
src/presentation/commands/create/
├── mod.rs // Module documentation and re-exports
├── handler.rs // Router that delegates to subcommands
├── errors.rs // Shared error types
├── config_loader.rs // Shared utilities (if needed)
├── subcommands/ // 🆕 Dedicated subcommands folder
│ ├── mod.rs // Subcommands module and re-exports
│ ├── environment.rs // Environment creation subcommand
│ └── template.rs // Template generation subcommand
└── tests/ // Test modules
├── mod.rs
├── integration.rs
└── fixtures.rs
```

**Key characteristics:**

- `handler.rs` acts as a simple router/dispatcher
- Each subcommand has its own focused module in `subcommands/`
- Subcommands are isolated and single-responsibility
- Easy to add new subcommands without cluttering main files

**Example:**

```rust
// In handler.rs (router)
pub fn handle_create_command(
action: CreateAction,
working_dir: &Path,
) -> Result<(), CreateSubcommandError> {
match action {
CreateAction::Environment { env_file } => {
subcommands::handle_environment_creation(&env_file, working_dir)
}
CreateAction::Template { output_path } => {
let template_path = output_path.unwrap_or_else(CreateAction::default_template_path);
subcommands::handle_template_generation(&template_path)
}
}
}

// In subcommands/environment.rs
pub fn handle_environment_creation(
env_file: &Path,
working_dir: &Path,
) -> Result<(), CreateSubcommandError> {
// Focused implementation for environment creation
}

// In subcommands/template.rs
pub fn handle_template_generation(
output_path: &Path,
) -> Result<(), CreateSubcommandError> {
// Focused implementation for template generation
}
```

### When to Use Each Pattern

**Use Pattern 1 (Simple Commands)** when:

- The command performs a single, focused operation
- No routing or branching logic is needed
- The implementation fits naturally in one module

**Use Pattern 2 (Commands with Subcommands)** when:

- The command has multiple distinct subcommands
- Each subcommand has significant implementation
- You want to isolate different behaviors for clarity
- You anticipate adding more subcommands in the future

### Benefits of These Patterns

✅ **Clear Visual Distinction**: Folder structure immediately shows command complexity
✅ **Consistent Naming**: All commands use `handler.rs` for their main entry point
✅ **Single Responsibility**: Each subcommand module has one clear purpose
✅ **Easy Extension**: Adding new subcommands is straightforward
✅ **Better Testing**: Each subcommand can be tested independently
✅ **Improved Navigation**: Developers can quickly find the right code

### Migration Guide

When refactoring existing commands to follow these patterns:

1. **For simple commands**: Rename `command.rs` → `handler.rs`
2. **For commands with subcommands**:
- Create `subcommands/` directory
- Move subcommand implementations to individual files in `subcommands/`
- Rename main file to `handler.rs` and simplify to a router
- Update `mod.rs` to include the `subcommands` module
- Update re-exports to use the new structure

**Example migration:**

```bash
# Before
create/
└── subcommand.rs (contains all logic)

# After
create/
├── handler.rs (router only)
└── subcommands/
├── mod.rs
├── environment.rs
└── template.rs
```

## 🔗 Related Documentation

- [Testing Conventions](./testing.md) - How to organize test code
Expand Down
43 changes: 43 additions & 0 deletions src/presentation/commands/create/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Create Command Handler
//!
//! This module handles the create command execution at the presentation layer,
//! routing between different subcommands (environment creation or template generation).

use std::path::Path;

use crate::presentation::cli::commands::CreateAction;

use super::errors::CreateSubcommandError;
use super::subcommands;

/// Handle the create command with its subcommands
///
/// This function routes between different create subcommands (environment or template).
///
/// # Arguments
///
/// * `action` - The create action to perform (environment creation or template generation)
/// * `working_dir` - Root directory for environment data storage
///
/// # Returns
///
/// Returns `Ok(())` on success, or a `CreateSubcommandError` on failure.
///
/// # Errors
///
/// Returns an error if the subcommand execution fails.
#[allow(clippy::result_large_err)] // Error contains detailed context for user guidance
pub fn handle_create_command(
action: CreateAction,
working_dir: &Path,
) -> Result<(), CreateSubcommandError> {
match action {
CreateAction::Environment { env_file } => {
subcommands::handle_environment_creation(&env_file, working_dir)
}
CreateAction::Template { output_path } => {
let template_path = output_path.unwrap_or_else(CreateAction::default_template_path);
subcommands::handle_template_generation(&template_path)
}
}
}
8 changes: 5 additions & 3 deletions src/presentation/commands/create/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
//!
//! - `config_loader` - Figment integration for JSON configuration loading
//! - `errors` - Presentation layer error types with `.help()` methods
//! - `subcommand` - Main command handler orchestrating the workflow
//! - `handler` - Main command handler routing between subcommands
//! - `subcommands` - Individual subcommand implementations (environment, template)
//!
//! ## Usage Example
//!
Expand All @@ -36,12 +37,13 @@

pub mod config_loader;
pub mod errors;
pub mod subcommand;
pub mod handler;
pub mod subcommands;

#[cfg(test)]
mod tests;

// Re-export commonly used types for convenience
pub use config_loader::ConfigLoader;
pub use errors::{ConfigFormat, CreateSubcommandError};
pub use subcommand::handle_create_command;
pub use handler::handle_create_command;
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
//! Create Subcommand Handler
//! Environment Creation Subcommand
//!
//! This module handles the create subcommand execution at the presentation layer,
//! including configuration file loading, argument processing, user interaction,
//! and command execution.
//! This module handles the environment creation subcommand for creating
//! deployment environments from configuration files.

use std::path::Path;
use std::sync::Arc;
Expand All @@ -11,99 +10,11 @@ use std::time::Duration;
use crate::application::command_handlers::create::CreateCommandHandler;
use crate::domain::config::EnvironmentCreationConfig;
use crate::infrastructure::persistence::repository_factory::RepositoryFactory;
use crate::presentation::cli::commands::CreateAction;
use crate::presentation::user_output::{UserOutput, VerbosityLevel};
use crate::shared::{Clock, SystemClock};

use super::config_loader::ConfigLoader;
use super::errors::CreateSubcommandError;

/// Handle the create command with its subcommands
///
/// This function routes between different create subcommands (environment or template).
///
/// # Arguments
///
/// * `action` - The create action to perform (environment creation or template generation)
/// * `working_dir` - Root directory for environment data storage
///
/// # Returns
///
/// Returns `Ok(())` on success, or a `CreateSubcommandError` on failure.
///
/// # Errors
///
/// Returns an error if the subcommand execution fails.
#[allow(clippy::result_large_err)] // Error contains detailed context for user guidance
pub fn handle_create_command(
action: CreateAction,
working_dir: &Path,
) -> Result<(), CreateSubcommandError> {
match action {
CreateAction::Environment { env_file } => {
handle_environment_creation(&env_file, working_dir)
}
CreateAction::Template { output_path } => {
let template_path = output_path.unwrap_or_else(CreateAction::default_template_path);
handle_template_generation(&template_path)
}
}
}

/// Handle template generation
///
/// This function generates a configuration template file with placeholder values
/// that users can edit to create their own environment configurations.
///
/// # Arguments
///
/// * `output_path` - Path where the template file should be created
///
/// # Returns
///
/// Returns `Ok(())` on success, or a `CreateSubcommandError` on failure.
///
/// # Errors
///
/// Returns an error if template file creation fails.
#[allow(clippy::result_large_err)] // Error contains detailed context for user guidance
fn handle_template_generation(output_path: &Path) -> Result<(), CreateSubcommandError> {
// Create user output for progress messages
let mut output = UserOutput::new(VerbosityLevel::Normal);

output.progress("Generating configuration template...");

// Call existing domain method - template generation implemented in PR #48
// This is async, so we need to use tokio runtime
tokio::runtime::Runtime::new()
.expect("Failed to create tokio runtime")
.block_on(async {
EnvironmentCreationConfig::generate_template_file(output_path)
.await
.map_err(CreateSubcommandError::TemplateGenerationFailed)
})?;

output.success(&format!(
"Configuration template generated: {}",
output_path.display()
));
println!();
println!("Next steps:");
println!("1. Edit the template file and replace placeholder values:");
println!(" - REPLACE_WITH_ENVIRONMENT_NAME: Choose a unique environment name (e.g., 'dev', 'staging')");
println!(" - REPLACE_WITH_SSH_PRIVATE_KEY_PATH: Path to your SSH private key");
println!(" - REPLACE_WITH_SSH_PUBLIC_KEY_PATH: Path to your SSH public key");
println!("2. Review default values:");
println!(" - username: 'torrust' (can be changed if needed)");
println!(" - port: 22 (standard SSH port)");
println!("3. Create the environment:");
println!(
" torrust-tracker-deployer create environment --env-file {}",
output_path.display()
);

Ok(())
}
use super::super::config_loader::ConfigLoader;
use super::super::errors::CreateSubcommandError;

/// Handle environment creation from configuration file
///
Expand Down Expand Up @@ -135,7 +46,7 @@ fn handle_template_generation(output_path: &Path) -> Result<(), CreateSubcommand
/// loaded, parsed, validated, or if the create command execution fails.
/// All errors include detailed context and actionable troubleshooting guidance.
#[allow(clippy::result_large_err)] // Error contains detailed context for user guidance
fn handle_environment_creation(
pub fn handle_environment_creation(
env_file: &Path,
working_dir: &Path,
) -> Result<(), CreateSubcommandError> {
Expand Down
10 changes: 10 additions & 0 deletions src/presentation/commands/create/subcommands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Create Subcommands Module
//!
//! This module contains the individual subcommands for the create command.

pub mod environment;
pub mod template;

// Re-export subcommand handlers for convenience
pub use environment::handle_environment_creation;
pub use template::handle_template_generation;
Loading
Loading