Skip to content

[Subissue 3/7] CLI Presentation Layer #37

@josecelano

Description

@josecelano

Overview

Implement the CLI presentation layer for the create command, handling Figment integration for configuration file parsing, argument processing, and user interaction.

Parent EPIC: #34 - Implement Create Environment Command
Depends On: #36 (Application Layer CreateCommand)
Related: Full Specification

Key Points

  • Add --working-dir flag to main CLI for production use (not just tests)
  • Figment stays in presentation layer as delivery mechanism
  • All errors use tiered help system with .help() methods

Goals

  • Create create subcommand in presentation layer

    • Figment integration for configuration file parsing (JSON format)
    • Argument parsing (--env-file, --working-dir)
    • Configuration file loading using Figment
    • Conversion from raw file data to clean domain objects
    • Call application layer CreateCommand with domain objects
    • User feedback and progress indication
    • Error message presentation with helpful context
  • Add explicit presentation error enums

    • CreateSubcommandError for CLI-specific errors
    • Error conversion from application layer to user-friendly messages
    • All errors implement .help() methods
  • Command help documentation

    • Usage examples
    • Argument descriptions
    • Error guidance
  • Tests

    • Integration tests for CLI interface with temporary directories
    • Unit tests for argument parsing and Figment integration
    • Error presentation tests

Module Structure

src/presentation/console/subcommands/create/
├── mod.rs                    # Module exports
├── subcommand.rs             # CreateSubcommand implementation
├── args.rs                   # CLI argument definitions
├── config_loader.rs          # Figment integration
├── errors.rs                 # Presentation error types with .help()
└── tests/
    ├── mod.rs
    ├── integration.rs        # CLI integration tests
    └── fixtures.rs           # Test fixtures

CLI Arguments

#[derive(Debug, Args)]
pub struct CreateArgs {
    #[command(subcommand)]
    pub action: CreateAction,
}

#[derive(Debug, Subcommand)]
pub enum CreateAction {
    Environment {
        /// Path to the environment configuration file
        #[arg(long, short = 'f', value_name = "FILE")]
        env_file: PathBuf,
    },
    // Future: Template generation
}

Configuration Loader (Figment)

pub struct ConfigLoader;

impl ConfigLoader {
    pub fn load_from_file(
        &self, 
        config_path: &Path
    ) -> Result<EnvironmentCreationConfig, CreateSubcommandError> {
        // Verify file exists
        if !config_path.exists() {
            return Err(CreateSubcommandError::ConfigFileNotFound {
                path: config_path.to_path_buf(),
            });
        }

        // Load with Figment
        let config = Figment::new()
            .merge(Serialized::defaults(EnvironmentCreationConfig::default()))
            .merge(Json::file(config_path))
            .extract()
            .map_err(|source| CreateSubcommandError::ConfigParsingFailed {
                path: config_path.to_path_buf(),
                format: ConfigFormat::Json,
                source: Box::new(source),
            })?;

        // Validate using domain rules
        config.validate()
            .map_err(CreateSubcommandError::ConfigValidationFailed)?;

        Ok(config)
    }
}

Error Types with .help()

#[derive(Debug, Error)]
pub enum CreateSubcommandError {
    #[error("Configuration file not found: {path}")]
    ConfigFileNotFound { path: PathBuf },

    #[error("Failed to parse configuration file: {path}")]
    ConfigParsingFailed {
        path: PathBuf,
        format: ConfigFormat,
        #[source]
        source: Box<dyn std::error::Error + Send + Sync>,
    },

    #[error("Configuration validation failed")]
    ConfigValidationFailed(#[source] ConfigValidationError),

    #[error("Command execution failed")]
    CommandFailed(#[source] CreateCommandError),
}

impl CreateSubcommandError {
    pub fn help(&self) -> &'static str {
        // Detailed troubleshooting guidance
    }
}

Usage Examples

# Create environment from configuration file
torrust-tracker-deployer create environment --env-file ./config/environment.json

# With custom working directory
torrust-tracker-deployer --working-dir /path/to/workspace create environment --env-file ./config/env.json

# Show help
torrust-tracker-deployer create --help
torrust-tracker-deployer create environment --help

Acceptance Criteria

  • create environment --env-file loads and parses JSON configuration
  • --working-dir flag works in production (main CLI args)
  • Invalid file paths rejected with clear errors
  • JSON parsing errors show clear messages
  • Validation errors show actionable guidance
  • Command help documentation is comprehensive
  • Error messages use tiered help system (.help() methods)
  • Integration tests cover all CLI scenarios
  • Figment integration tested with various JSON formats

Estimated Time

3-4 hours

Notes

  • Figment integration stays in presentation layer (DDD boundary)
  • Support --working-dir at main CLI level for production use
  • All errors must be user-friendly with .help() guidance
  • TOML support will be added in future separate issue

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions