opendeviationbar-streaming 13.75.0

Real-time streaming engine for open deviation bar processing
Documentation
//! ClickHouse writer configuration.
//!
//! Issue #318: Rust-native ClickHouse writer — config.

/// Configuration for the ClickHouse writer flush behavior.
///
/// Defaults: N=500 rows, T=100ms, bytes=5MB. Override via env vars
/// (`OPENDEVIATIONBAR_CH_WRITER_*`) or construct directly.
#[derive(Debug, Clone)]
pub struct ClickHouseWriterConfig {
    /// ClickHouse HTTP URL (e.g., "http://localhost:8123").
    pub url: String,
    /// Database name (default: "opendeviationbar_cache").
    pub database: String,
    /// Table name (default: "open_deviation_bars").
    pub table: String,
    /// Max bars before flush (default: 500).
    pub max_rows: usize,
    /// Max milliseconds before flush (default: 100).
    pub flush_period_ms: u64,
    /// Max bytes before flush (default: 5MB = 5_242_880).
    pub max_bytes: usize,
    /// Bounded channel capacity for `FlushCommand` (default: 10_000).
    pub channel_capacity: usize,
    /// Max retry attempts for transient errors (default: 5).
    pub max_retries: u32,
}

impl Default for ClickHouseWriterConfig {
    fn default() -> Self {
        Self {
            url: "http://localhost:8123".to_string(),
            database: "opendeviationbar_cache".to_string(),
            table: "open_deviation_bars".to_string(),
            max_rows: 500,
            flush_period_ms: 100,
            max_bytes: 5_242_880, // 5 MB
            channel_capacity: 10_000,
            max_retries: 5,
        }
    }
}

impl ClickHouseWriterConfig {
    /// Build config from environment variables, falling back to defaults.
    ///
    /// Reads:
    /// - `OPENDEVIATIONBAR_CH_HOSTS` -> url (prepends "http://" and appends ":8123" if bare hostname)
    /// - `OPENDEVIATIONBAR_CH_WRITER_MAX_ROWS` -> max_rows
    /// - `OPENDEVIATIONBAR_CH_WRITER_FLUSH_MS` -> flush_period_ms
    /// - `OPENDEVIATIONBAR_CH_WRITER_MAX_BYTES` -> max_bytes
    pub fn from_env() -> Self {
        let mut config = Self::default();

        if let Ok(hosts) = std::env::var("OPENDEVIATIONBAR_CH_HOSTS") {
            let host = hosts.split(',').next().unwrap_or("localhost").trim();
            config.url = if host.starts_with("http://") || host.starts_with("https://") {
                host.to_string()
            } else {
                format!("http://{host}:8123")
            };
        }

        if let Ok(val) = std::env::var("OPENDEVIATIONBAR_CH_WRITER_MAX_ROWS")
            && let Ok(n) = val.parse::<usize>()
        {
            config.max_rows = n;
        }

        if let Ok(val) = std::env::var("OPENDEVIATIONBAR_CH_WRITER_FLUSH_MS")
            && let Ok(n) = val.parse::<u64>()
        {
            config.flush_period_ms = n;
        }

        if let Ok(val) = std::env::var("OPENDEVIATIONBAR_CH_WRITER_MAX_BYTES")
            && let Ok(n) = val.parse::<usize>()
        {
            config.max_bytes = n;
        }

        config
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_defaults() {
        let config = ClickHouseWriterConfig::default();
        assert_eq!(config.max_rows, 500);
        assert_eq!(config.flush_period_ms, 100);
        assert_eq!(config.max_bytes, 5_242_880);
        assert_eq!(config.channel_capacity, 10_000);
        assert_eq!(config.max_retries, 5);
        assert_eq!(config.database, "opendeviationbar_cache");
        assert_eq!(config.table, "open_deviation_bars");
        assert_eq!(config.url, "http://localhost:8123");
    }

    #[test]
    fn test_config_from_env_defaults() {
        // Without setting any env vars, from_env returns defaults
        let config = ClickHouseWriterConfig::from_env();
        assert_eq!(config.max_rows, 500);
        assert_eq!(config.flush_period_ms, 100);
        assert_eq!(config.max_bytes, 5_242_880);
    }
}