Skip to content

[Subissue 3/7] CLI Presentation Layer #37

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

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions