# Rustelo Configuration Traits Guide

## Overview

Rustelo's configuration system is built around a flexible trait-based architecture that solves the common problems of configuration management in Rust web applications, particularly those targeting WASM environments.

### Core Problems Solved

1. **ENV vs Config File Sprawl** - Unifies configuration sources with clear precedence
2. **WASM Configuration Access** - Generates compile-time constants for WASM builds
3. **Testing Complexity** - Provides mockable trait-based interfaces
4. **Configuration Validation** - Built-in validation and error handling
5. **Layered Overrides** - Hierarchical configuration with clear precedence rules

## Architecture Overview

```mermaid
graph TB
    A[ConfigResolver] --> B[LayeredResolver]
    A --> C[WasmConstantGenerator]
    A --> D[BuildTimeResolver]
    E[Config Files] --> F[Resolution Engine]
    G[ENV Variables] --> F
    H[Feature Flags] --> F
    F --> I[Validated Config]
    I --> J[Server Runtime]
    I --> K[WASM Constants]
```

## Core Traits

### 1. ConfigResolver

The fundamental trait for configuration resolution.

```rust
pub trait ConfigResolver {
    type Config: Clone + Send + Sync + 'static;
    type Error: StdError + Send + Sync + 'static;
    
    fn resolve(&mut self) -> Result<Self::Config, Self::Error>;
    fn resolve_section<T>(&mut self, section: &str) -> Result<T, Self::Error>;
    fn has_changed(&self) -> bool;
    fn clear_cache(&mut self);
}
```

**When to implement:** Every Rustelo application should have a config resolver, either using the default implementation or creating a custom one.

**Example implementation:**
```rust
struct MyConfigResolver {
    project_root: PathBuf,
    cache: HashMap<String, MyConfig>,
}

impl ConfigResolver for MyConfigResolver {
    type Config = MyConfig;
    type Error = MyConfigError;
    
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        // 1. Start with defaults
        let mut config = MyConfig::default();
        
        // 2. Apply TOML files in order
        if let Ok(file_config) = self.load_from_file("config.toml") {
            config.merge(file_config);
        }
        
        // 3. Apply environment overrides
        config.apply_env_overrides();
        
        // 4. Validate
        config.validate()?;
        
        Ok(config)
    }
}
```

### 2. LayeredResolver

Extends ConfigResolver with hierarchical configuration layers.

```rust
pub trait LayeredResolver: ConfigResolver {
    fn add_layer(&mut self, layer: ConfigLayer) -> Result<(), Self::Error>;
    fn remove_layer(&mut self, layer_id: &str) -> Result<(), Self::Error>;
    fn refresh_layers(&mut self) -> Result<(), Self::Error>;
    fn get_layers(&self) -> &[ConfigLayer];
}
```

**Configuration Hierarchy:**
1. **Local overrides** (precedence: 100) - `config/local.toml` (git-ignored)
2. **Environment configs** (precedence: 90) - `config/dev.toml`, `config/prod.toml`
3. **Feature configs** (precedence: 75) - `config/features/auth.toml`
4. **Template defaults** (precedence: 50) - `config/template.toml`
5. **Framework defaults** (precedence: 25) - Built-in defaults

**Example usage:**
```rust
let mut resolver = MyLayeredResolver::new(project_root)?;

// Add custom layer
resolver.add_layer(ConfigLayer::Feature(
    "custom-auth".to_string(), 
    PathBuf::from("config/custom-auth.toml")
))?;

// Resolution applies all layers in precedence order
let config = resolver.resolve()?;
```

### 3. WasmConstantGenerator

Generates compile-time constants for WASM consumption.

```rust
pub trait WasmConstantGenerator {
    type Config;
    
    fn generate_constants(&self, config: &Self::Config) -> WasmConstants;
    fn write_constants_file(&self, constants: &WasmConstants, out_path: &Path) -> Result<(), Box<dyn StdError>>;
    fn generate_getters(&self, constants: &WasmConstants) -> String;
}
```

**Problem solved:** WASM cannot access environment variables or filesystem at runtime. This trait generates compile-time constants that WASM can use.

**Example:**
```rust
impl WasmConstantGenerator for MyResolver {
    type Config = MyConfig;
    
    fn generate_constants(&self, config: &Self::Config) -> WasmConstants {
        let mut constants = WasmConstants::new();
        constants.add_string("API_BASE_URL", &config.api_base_url);
        constants.add_numeric("TIMEOUT_MS", config.timeout_ms as i64);
        constants.add_boolean("DEBUG_MODE", config.debug);
        constants
    }
}
```

Generated output:
```rust
// Generated in OUT_DIR/config_constants.rs
pub const API_BASE_URL: &str = "https://api.example.com";
pub const TIMEOUT_MS: i64 = 5000;
pub const DEBUG_MODE: bool = true;

pub fn get_api_base_url() -> &'static str { API_BASE_URL }
pub fn get_timeout_ms() -> i64 { TIMEOUT_MS }
pub fn is_debug_mode() -> bool { DEBUG_MODE }
```

### 4. BuildTimeResolver

Handles configuration resolution during build process.

```rust
pub trait BuildTimeResolver {
    type Config: Clone + Send + Sync + 'static;
    type Error: StdError + Send + Sync + 'static;
    
    fn resolve_for_build(project_root: &Path) -> Result<Self::Config, Self::Error>;
    fn generate_build_artifacts(config: &Self::Config, out_dir: &Path) -> Result<(), Box<dyn StdError>>;
    fn get_watch_files(project_root: &Path) -> Vec<PathBuf>;
}
```

**Used in build.rs:**
```rust
fn main() -> Result<(), Box<dyn Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    
    // Resolve config at build time
    let config = MyResolver::resolve_for_build(Path::new(&manifest_dir))?;
    
    // Generate artifacts
    MyResolver::generate_build_artifacts(&config, Path::new(&out_dir))?;
    
    // Set up watch files for rebuild triggers
    for watch_file in MyResolver::get_watch_files(Path::new(&manifest_dir)) {
        println!("cargo:rerun-if-changed={}", watch_file.display());
    }
    
    Ok(())
}
```

## Configuration Sources

### ConfigSource Trait

Allows pluggable configuration sources.

```rust
pub trait ConfigSource {
    type Error: StdError + Send + Sync + 'static;
    
    fn id(&self) -> &str;
    fn precedence(&self) -> u8;
    fn load(&self) -> Result<ConfigData, Self::Error>;
    fn has_changed(&self) -> bool;
}
```

### Built-in Sources

1. **TOML Files** - `TomlFileSource`
2. **Environment Variables** - `EnvSource`
3. **JSON Files** - `JsonFileSource`
4. **HTTP Endpoints** - `HttpSource`
5. **Database** - `DatabaseSource`

### Custom Sources

```rust
struct RedisConfigSource {
    client: redis::Client,
    key: String,
}

impl ConfigSource for RedisConfigSource {
    type Error = MyError;
    
    fn id(&self) -> &str { "redis" }
    fn precedence(&self) -> u8 { 80 } // High precedence
    
    fn load(&self) -> Result<ConfigData, Self::Error> {
        let mut conn = self.client.get_connection()?;
        let content: String = conn.get(&self.key)?;
        
        Ok(ConfigData {
            content,
            format: ConfigFormat::Json,
            metadata: ConfigMetadata {
                source_id: self.id().to_string(),
                modified: Some(SystemTime::now()),
                extra: HashMap::new(),
            },
        })
    }
}
```

## Configuration Layers in Detail

### ConfigLayer Enum

```rust
pub enum ConfigLayer {
    Local(PathBuf),                    // precedence: 100
    Environment(String, PathBuf),      // precedence: 90
    Feature(String, PathBuf),          // precedence: 75
    Template(PathBuf),                 // precedence: 50
    Framework,                         // precedence: 25
}
```

### Layer Discovery

The system automatically discovers layers from your project structure:

```
project_root/
├── config/
│   ├── template.toml       # Template layer
│   ├── development.toml    # Environment layer (dev)
│   ├── production.toml     # Environment layer (prod)
│   ├── local.toml         # Local layer (git-ignored)
│   └── features/          # Feature layers
│       ├── auth.toml
│       ├── analytics.toml
│       └── metrics.toml
├── .env                   # Environment variables
└── config.toml           # Alternative config location
```

### Layer Merging

Configuration merging follows these rules:

1. **Primitive values** - Higher precedence overwrites lower
2. **Objects** - Merged recursively, field by field
3. **Arrays** - Higher precedence replaces entire array
4. **Optional fields** - `None` doesn't override `Some(value)`

```rust
impl Mergeable for MyConfig {
    fn merge(&mut self, other: Self) {
        // Merge server config (only non-default values)
        if other.server.host != ServerConfig::default().host {
            self.server.host = other.server.host;
        }
        
        // Merge feature flags (additive)
        for (key, value) in other.features {
            self.features.insert(key, value);
        }
        
        // Arrays replace completely
        if !other.allowed_origins.is_empty() {
            self.allowed_origins = other.allowed_origins;
        }
    }
}
```

## WASM Constants System

### WasmConstants Structure

```rust
pub struct WasmConstants {
    pub string_constants: HashMap<String, String>,
    pub numeric_constants: HashMap<String, i64>,
    pub boolean_constants: HashMap<String, bool>,
    pub path_constants: HashMap<String, PathBuf>,
}
```

### Generated Output

The system generates three types of artifacts:

1. **Constants file** - Raw const declarations
2. **Getter functions** - Type-safe accessors
3. **Runtime helpers** - Utility functions

```rust
// Generated config_constants.rs

// Raw constants
pub const SERVER_HOST: &str = "localhost";
pub const SERVER_PORT: i64 = 3030;
pub const DEBUG_MODE: bool = true;

// Getter functions
pub fn get_server_host() -> &'static str { SERVER_HOST }
pub fn get_server_port() -> i64 { SERVER_PORT }
pub fn is_debug_mode() -> bool { DEBUG_MODE }

// Runtime helpers
pub fn get_server_url() -> String {
    format!("http://{}:{}", SERVER_HOST, SERVER_PORT)
}

pub fn get_api_endpoint(path: &str) -> String {
    format!("{}/api{}", get_server_url(), path)
}
```

### Including in Code

```rust
// In your WASM-targeting code
include!(concat!(env!("OUT_DIR"), "/config_constants.rs"));

// Usage
#[wasm_bindgen]
pub fn get_api_url() -> String {
    get_server_url()
}

// In components
#[component]
pub fn ApiStatus() -> impl IntoView {
    let endpoint = get_api_endpoint("/health");
    
    view! {
        <div>"API Status: " {endpoint}</div>
    }
}
```

## Validation System

### Validatable Trait

```rust
pub trait Validatable {
    type Error: StdError + Send + Sync + 'static;
    
    fn validate(&self) -> Result<(), Self::Error>;
    fn warnings(&self) -> Vec<String>;
}
```

### Implementation Example

```rust
impl Validatable for MyConfig {
    type Error = MyConfigError;
    
    fn validate(&self) -> Result<(), Self::Error> {
        // Required fields
        if self.server.host.is_empty() {
            return Err(MyConfigError::ValidationError("Server host is required".into()));
        }
        
        // Value ranges
        if self.server.port == 0 || self.server.port > 65535 {
            return Err(MyConfigError::ValidationError("Invalid port number".into()));
        }
        
        // Path existence
        if !self.content_path.exists() {
            return Err(MyConfigError::ValidationError(
                format!("Content path does not exist: {}", self.content_path.display())
            ));
        }
        
        // Cross-field validation
        if self.environment == "production" && self.debug {
            return Err(MyConfigError::ValidationError(
                "Debug mode should not be enabled in production".into()
            ));
        }
        
        Ok(())
    }
    
    fn warnings(&self) -> Vec<String> {
        let mut warnings = Vec::new();
        
        if self.server.port < 1024 && !cfg!(unix) {
            warnings.push("Using privileged port on non-Unix system".to_string());
        }
        
        if !self.cache_enabled && self.environment == "production" {
            warnings.push("Cache disabled in production - performance impact".to_string());
        }
        
        warnings
    }
}
```

## Error Handling

### ConfigError Enum

```rust
pub enum ConfigError {
    FileNotFound(PathBuf),
    ParseError(String),
    LayerNotFound(String),
    ResolutionFailed(String),
    IoError(std::io::Error),
    Custom(String),
}
```

### Error Context

The system provides detailed error context:

```rust
// Example error messages
ConfigError::FileNotFound("/project/config/production.toml")
// -> "Configuration file not found: /project/config/production.toml"

ConfigError::ParseError("Invalid TOML at line 15: expected '=', found ':'")
// -> "Configuration parse error: Invalid TOML at line 15: expected '=', found ':'"

ConfigError::ResolutionFailed("Layer 'auth' requires 'database' layer but it's not available")
// -> "Configuration resolution failed: Layer 'auth' requires 'database' layer but it's not available"
```

## Testing Configuration

### Mock Resolver

```rust
#[cfg(test)]
struct MockConfigResolver {
    config: MyConfig,
}

impl ConfigResolver for MockConfigResolver {
    type Config = MyConfig;
    type Error = MyConfigError;
    
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        Ok(self.config.clone())
    }
    
    fn resolve_section<T>(&mut self, _section: &str) -> Result<T, Self::Error> {
        // Mock implementation
        todo!()
    }
}

#[test]
fn test_with_mock_config() {
    let mock_config = MyConfig {
        server: ServerConfig {
            host: "test.example.com".to_string(),
            port: 8080,
            debug: false,
        },
        // ... other fields
    };
    
    let mut resolver = MockConfigResolver { config: mock_config };
    let config = resolver.resolve().unwrap();
    
    assert_eq!(config.server.host, "test.example.com");
    assert_eq!(config.server.port, 8080);
}
```

### Integration Tests

```rust
#[test]
fn test_layered_config_resolution() {
    let temp_dir = tempdir().unwrap();
    let config_dir = temp_dir.path().join("config");
    fs::create_dir_all(&config_dir).unwrap();
    
    // Create test config files
    let base_config = r#"
[server]
host = "localhost"
port = 3030
debug = true
"#;
    fs::write(config_dir.join("template.toml"), base_config).unwrap();
    
    let prod_config = r#"
[server]
host = "production.example.com"
debug = false
"#;
    fs::write(config_dir.join("production.toml"), prod_config).unwrap();
    
    // Test resolution
    std::env::set_var("RUSTELO_ENV", "production");
    let mut resolver = MyResolver::new(temp_dir.path().to_path_buf()).unwrap();
    let config = resolver.resolve().unwrap();
    
    // Verify merging worked correctly
    assert_eq!(config.server.host, "production.example.com"); // From production layer
    assert_eq!(config.server.port, 3030); // From template layer
    assert_eq!(config.server.debug, false); // From production layer
}
```

## Best Practices

### 1. Configuration Structure

```rust
// ✅ Good: Organized, typed configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
    pub server: ServerConfig,
    pub database: DatabaseConfig,
    pub features: FeatureConfig,
    pub logging: LoggingConfig,
}

// ❌ Bad: Flat, stringly-typed configuration  
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
    pub server_host: String,
    pub server_port: String, // Should be u16
    pub db_url: String,
    pub enable_auth: String, // Should be bool
    pub log_level: String,   // Should be enum
}
```

### 2. Environment Overrides

```rust
// ✅ Good: Selective overrides for sensitive values
impl MyConfig {
    fn apply_env_overrides(&mut self) {
        if let Ok(db_url) = env::var("DATABASE_URL") {
            self.database.url = db_url;
        }
        if let Ok(secret_key) = env::var("SECRET_KEY") {
            self.auth.secret_key = secret_key;
        }
    }
}

// ❌ Bad: Everything from environment
impl MyConfig {
    fn from_env() -> Self {
        Self {
            server_host: env::var("SERVER_HOST").unwrap_or_default(),
            server_port: env::var("SERVER_PORT").unwrap_or_default(),
            app_name: env::var("APP_NAME").unwrap_or_default(),
            // ... dozens more env::var calls
        }
    }
}
```

### 3. Validation Placement

```rust
// ✅ Good: Validate after full resolution
impl ConfigResolver for MyResolver {
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        let mut config = self.resolve_layers()?;
        config.apply_env_overrides();
        config.validate()?; // Validate complete configuration
        Ok(config)
    }
}

// ❌ Bad: Validate each layer separately
impl ConfigResolver for MyResolver {
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        let mut config = MyConfig::default();
        
        for layer in &self.layers {
            let layer_config = self.load_layer(layer)?;
            layer_config.validate()?; // Partial validation doesn't work
            config.merge(layer_config);
        }
        
        Ok(config)
    }
}
```

### 4. WASM Constants

```rust
// ✅ Good: Only include necessary constants
impl WasmConstantGenerator for MyResolver {
    fn generate_constants(&self, config: &Self::Config) -> WasmConstants {
        let mut constants = WasmConstants::new();
        
        // Only WASM-needed values
        constants.add_string("API_BASE_URL", &config.api_base_url);
        constants.add_boolean("DEBUG_MODE", config.debug);
        constants.add_numeric("TIMEOUT_MS", config.client_timeout_ms as i64);
        
        constants
    }
}

// ❌ Bad: Include everything (bloats WASM bundle)
impl WasmConstantGenerator for MyResolver {
    fn generate_constants(&self, config: &Self::Config) -> WasmConstants {
        let mut constants = WasmConstants::new();
        
        // Too much data for WASM
        constants.add_string("DATABASE_URL", &config.database.url); // Security risk!
        constants.add_string("SECRET_KEY", &config.auth.secret_key); // Security risk!
        constants.add_string("FULL_CONFIG_JSON", &serde_json::to_string(config).unwrap()); // Too big!
        
        constants
    }
}
```

### 5. Layer Organization

```
project/
├── config/
│   ├── template.toml          # ✅ Framework defaults
│   ├── development.toml       # ✅ Dev environment
│   ├── staging.toml          # ✅ Staging environment  
│   ├── production.toml       # ✅ Production environment
│   ├── local.toml           # ✅ Local overrides (gitignored)
│   └── features/            # ✅ Feature-specific config
│       ├── auth.toml
│       ├── analytics.toml
│       └── metrics.toml
└── .env                     # ✅ Runtime environment variables
```

Avoid:
```
project/
├── config.dev.toml          # ❌ Inconsistent naming
├── config.prod.toml         # ❌ Inconsistent naming
├── auth-config.toml         # ❌ Should be in features/
├── local-overrides.toml     # ❌ Should be local.toml
└── .env.development         # ❌ Too many env files
```

## Migration Guide

### From Environment Variables

**Before:**
```rust
fn load_config() -> Config {
    Config {
        server_host: env::var("SERVER_HOST").unwrap_or("localhost".to_string()),
        server_port: env::var("SERVER_PORT").unwrap_or("3030".to_string()).parse().unwrap(),
        database_url: env::var("DATABASE_URL").expect("DATABASE_URL required"),
        debug: env::var("DEBUG").unwrap_or("false".to_string()).parse().unwrap(),
    }
}
```

**After:**
```rust
// config/development.toml
[server]
host = "localhost"
port = 3030
debug = true

[database]
url = "postgresql://localhost:5432/myapp_dev"

// Resolver with env override for sensitive values
impl ConfigResolver for MyResolver {
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        let mut config = self.resolve_toml_layers()?;
        
        // Only override sensitive values from env
        if let Ok(database_url) = env::var("DATABASE_URL") {
            config.database.url = database_url;
        }
        
        config.validate()?;
        Ok(config)
    }
}
```

### From Manual WASM Constants

**Before (in build.rs):**
```rust
fn main() {
    let host = env::var("SERVER_HOST").unwrap_or("localhost".to_string());
    let port = env::var("SERVER_PORT").unwrap_or("3030".to_string());
    
    let constants = format!(r#"
pub const SERVER_HOST: &str = "{}";
pub const SERVER_PORT: &str = "{}";
"#, host, port);
    
    let out_dir = env::var("OUT_DIR").unwrap();
    fs::write(Path::new(&out_dir).join("constants.rs"), constants).unwrap();
}
```

**After:**
```rust
fn main() -> Result<(), Box<dyn Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    
    // Use trait-based resolution
    let config = MyResolver::resolve_for_build(Path::new(&manifest_dir))?;
    MyResolver::generate_build_artifacts(&config, Path::new(&out_dir))?;
    
    Ok(())
}
```

## Common Patterns

### 1. Environment-Specific Configuration

```rust
// config/development.toml
[server]
host = "localhost"
port = 3030
debug = true

[database]
pool_size = 5

// config/production.toml  
[server]
host = "0.0.0.0"
port = 80
debug = false

[database]
pool_size = 20
```

### 2. Feature-Specific Configuration

```rust
// config/features/auth.toml
[auth]
jwt_secret = "dev-secret"
jwt_expiry_hours = 24
enable_2fa = false

[oauth]
google_client_id = "dev-client-id"

// config/features/analytics.toml
[analytics]
enabled = true
batch_size = 100
flush_interval_seconds = 60
```

### 3. Local Development Overrides

```rust
// config/local.toml (git-ignored)
[server]
port = 8080  # Different port for local development

[database]
url = "postgresql://localhost:5432/myapp_local"

[features]
experimental_ui = true
```

### 4. Build-Time Feature Detection

```rust
// In build.rs
fn generate_feature_constants() -> Result<(), Box<dyn Error>> {
    let mut constants = WasmConstants::new();
    
    // Generate constants based on Cargo features
    constants.add_boolean("HAS_AUTH", cfg!(feature = "auth"));
    constants.add_boolean("HAS_METRICS", cfg!(feature = "metrics"));
    constants.add_boolean("HAS_TLS", cfg!(feature = "tls"));
    
    // Environment-specific features
    let env = env::var("RUSTELO_ENV").unwrap_or("development".to_string());
    constants.add_boolean("IS_PRODUCTION", env == "production");
    
    // Write to file
    let code = constants.generate_rust_code();
    fs::write(Path::new(&env::var("OUT_DIR")?).join("features.rs"), code)?;
    
    Ok(())
}
```

### 5. Configuration Composition

```rust
struct CompositeResolver {
    toml_resolver: TomlResolver,
    redis_resolver: RedisResolver,
    fallback_resolver: DefaultResolver,
}

impl ConfigResolver for CompositeResolver {
    type Config = MyConfig;
    type Error = MyError;
    
    fn resolve(&mut self) -> Result<Self::Config, Self::Error> {
        // Try Redis first
        if let Ok(config) = self.redis_resolver.resolve() {
            return Ok(config);
        }
        
        // Fall back to TOML
        if let Ok(config) = self.toml_resolver.resolve() {
            return Ok(config);
        }
        
        // Final fallback to defaults
        self.fallback_resolver.resolve()
    }
}
```

This configuration traits system provides a robust, flexible foundation for managing configuration in Rustelo applications while solving the specific challenges of WASM deployment and complex application requirements.
