Skip to content

Implement domain configuration layer for environment creation#42

Merged
josecelano merged 2 commits into
mainfrom
copilot/implement-core-configuration-infrastructure
Oct 25, 2025
Merged

Implement domain configuration layer for environment creation#42
josecelano merged 2 commits into
mainfrom
copilot/implement-core-configuration-infrastructure

Conversation

Copilot AI commented Oct 25, 2025

Copy link
Copy Markdown
Contributor

Adds configuration infrastructure in the domain layer to bridge external configuration sources (JSON, CLI) and the internal domain model for environment creation.

Changes

Configuration Value Objects

  • EnvironmentCreationConfig - Top-level config with to_environment_params() converting to Environment::new() parameters
  • SshCredentialsConfig - Config layer representation (distinct from adapters::ssh::SshCredentials), supports defaults (username: "torrust", port: 22)
  • EnvironmentSection - Environment-specific settings container

Type Conversion

  • String → EnvironmentName (validates lowercase, dashes, no leading numbers)
  • String → Username (validates Linux requirements, 1-32 chars)
  • String → PathBuf (validates file existence for SSH keys)

Error Handling

  • CreateConfigError enum with variants: InvalidEnvironmentName, InvalidUsername, PrivateKeyNotFound, PublicKeyNotFound, InvalidPort
  • All variants implement .help() providing actionable troubleshooting guidance

Module Structure

src/domain/config/
├── mod.rs                          # Module exports and docs
├── environment_config.rs           # EnvironmentCreationConfig
├── ssh_credentials_config.rs       # SshCredentialsConfig
└── errors.rs                       # CreateConfigError

Usage

use torrust_tracker_deployer_lib::domain::config::EnvironmentCreationConfig;
use torrust_tracker_deployer_lib::domain::Environment;

let json = r#"{
    "environment": { "name": "dev" },
    "ssh_credentials": {
        "private_key_path": "fixtures/testing_rsa",
        "public_key_path": "fixtures/testing_rsa.pub"
    }
}"#;

let config: EnvironmentCreationConfig = serde_json::from_str(json)?;
let (name, credentials, port) = config.to_environment_params()?;
let environment = Environment::new(name, credentials, port);

Test Coverage

26 unit tests covering deserialization, type conversion, validation, error handling, and integration with Environment::new().


Part of Epic #34 (Create Environment Command). Enables application command handlers (#36), CLI integration (#37), and E2E tests (#38).

Original prompt

This section details on the original issue you should resolve

<issue_title>[Subissue 1/7] Core Configuration Infrastructure</issue_title>
<issue_description>## Overview

Implement core configuration infrastructure in the domain layer with pure configuration value objects, business validation rules, and explicit error handling.

Parent EPIC: #34 - Implement Create Environment Command
Related: Full Specification | Roadmap Task 1.5

Key Architecture Points

  • Configuration uses SshCredentialsConfig (distinct from adapters::ssh::SshCredentials)
  • Converts config strings to domain types (Username, PathBuf)
  • JSON Schema validation is optional - can defer to focus on domain validation first
  • All errors implement .help() methods (tiered help system pattern)

Goals

  • Create configuration value objects that convert to existing domain types

    • EnvironmentCreationConfig with to_environment_params() method
    • SshCredentialsConfig (config layer) distinct from adapters::ssh::SshCredentials
    • Convert string username to shared::Username type
    • Convert string paths to PathBuf
    • Use existing EnvironmentName::new() validation
  • Configuration validation

    • Primary validation via domain layer (EnvironmentName, Username, file existence)
    • Follow existing error handling patterns with thiserror and .help() methods
    • Optional: Secondary validation via JSON Schema
  • Add comprehensive unit tests

    • Test conversion to Environment::new() parameters
    • Test Username type conversion from string
    • Test compatibility with repository.save(&environment.into_any())
    • Test invalid username and environment name handling

Module Structure

src/domain/config/
├── mod.rs                    # Module exports
├── environment_config.rs     # EnvironmentCreationConfig value object
├── ssh_credentials_config.rs # SshCredentialsConfig value object
├── errors.rs                 # ConfigValidationError enum with .help()
├── schema.rs                 # Optional: JSON Schema validation
└── validation.rs             # Optional: Domain validation helpers

Key Types

EnvironmentCreationConfig

pub struct EnvironmentCreationConfig {
    pub environment: EnvironmentSection,
    pub ssh_credentials: SshCredentialsConfig,
}

impl EnvironmentCreationConfig {
    pub fn to_environment_params(self) 
        -> Result<(EnvironmentName, SshCredentials, u16), CreateConfigError> {
        // Convert to existing domain types
    }
}

SshCredentialsConfig

pub struct SshCredentialsConfig {
    pub private_key_path: String,
    pub public_key_path: String,
    #[serde(default = "default_ssh_username")]
    pub username: String,
    #[serde(default = "default_ssh_port")]
    pub port: u16,
}

Error Types with .help()

#[derive(Debug, Error)]
pub enum CreateConfigError {
    #[error("Invalid environment name")]
    InvalidEnvironmentName(#[source] EnvironmentNameError),
    
    #[error("Invalid SSH username")]
    InvalidUsername(#[source] UsernameError),
    
    // ... other variants
}

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

Acceptance Criteria

  • EnvironmentCreationConfig deserializes from JSON
  • to_environment_params() converts to domain types successfully
  • Invalid environment names rejected with clear errors
  • Invalid usernames rejected with clear errors
  • SSH key file validation (existence checks)
  • All errors implement .help() with detailed guidance
  • Unit tests cover all conversion and validation scenarios
  • Integration with existing Environment::new() verified

Dependencies

[dependencies]
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"

# Optional (can defer):
jsonschema = "0.21"  # JSON Schema validation
serde_json = "1.0"   # JSON Schema handling

Estimated Time

2-3 hours

Notes

  • JSON Schema validation is optional enhancement - domain validation is primary
  • Focus on type-safe conversion from config strings to domain types
  • Maintain clean separation: config layer vs domain/adapters layer
  • All error messages must be actionable with .help() methods</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: josecelano <58816+josecelano@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement core configuration infrastructure in the domain layer Implement domain configuration layer for environment creation Oct 25, 2025
Copilot AI requested a review from josecelano October 25, 2025 15:13
@josecelano
josecelano marked this pull request as ready for review October 25, 2025 16:18
@josecelano

Copy link
Copy Markdown
Member

ACK fea50f4

@josecelano
josecelano merged commit 376a336 into main Oct 25, 2025
27 checks passed
@josecelano
josecelano deleted the copilot/implement-core-configuration-infrastructure branch April 15, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Subissue 1/7] Core Configuration Infrastructure

2 participants