chie_core/
config.rs

1//! Configuration management for CHIE node settings.
2//!
3//! This module provides centralized configuration with validation, defaults,
4//! and builder patterns for easy construction.
5//!
6//! # Example
7//!
8//! ```rust
9//! use chie_core::config::{NodeSettings, StorageSettings, NetworkSettings};
10//!
11//! // Use builder pattern
12//! let settings = NodeSettings::builder()
13//!     .storage(StorageSettings::default())
14//!     .network(NetworkSettings::default())
15//!     .build()
16//!     .expect("Invalid configuration");
17//!
18//! println!("Max storage: {} GB", settings.storage.max_bytes_gb());
19//! ```
20
21use std::path::PathBuf;
22
23/// Storage configuration settings.
24#[derive(Debug, Clone)]
25pub struct StorageSettings {
26    /// Base path for storing content chunks.
27    pub base_path: PathBuf,
28    /// Maximum storage in bytes.
29    pub max_bytes: u64,
30    /// Minimum free space to maintain (bytes).
31    pub min_free_bytes: u64,
32    /// Enable tiered storage (SSD/HDD).
33    pub enable_tiering: bool,
34    /// SSD tier path (if tiering enabled).
35    pub ssd_path: Option<PathBuf>,
36    /// HDD tier path (if tiering enabled).
37    pub hdd_path: Option<PathBuf>,
38}
39
40impl StorageSettings {
41    /// Create storage settings with specified path and max bytes.
42    #[inline]
43    #[must_use]
44    pub fn new(base_path: PathBuf, max_bytes: u64) -> Self {
45        Self {
46            base_path,
47            max_bytes,
48            min_free_bytes: 1024 * 1024 * 1024, // 1 GB
49            enable_tiering: false,
50            ssd_path: None,
51            hdd_path: None,
52        }
53    }
54
55    /// Get max storage in gigabytes.
56    #[inline]
57    #[must_use]
58    pub const fn max_bytes_gb(&self) -> f64 {
59        self.max_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
60    }
61
62    /// Validate storage settings.
63    pub fn validate(&self) -> Result<(), ConfigError> {
64        if self.max_bytes == 0 {
65            return Err(ConfigError::InvalidValue(
66                "max_bytes must be greater than 0".into(),
67            ));
68        }
69
70        if self.min_free_bytes >= self.max_bytes {
71            return Err(ConfigError::InvalidValue(
72                "min_free_bytes must be less than max_bytes".into(),
73            ));
74        }
75
76        if self.enable_tiering && (self.ssd_path.is_none() || self.hdd_path.is_none()) {
77            return Err(ConfigError::InvalidValue(
78                "Tiering enabled but SSD/HDD paths not specified".into(),
79            ));
80        }
81
82        Ok(())
83    }
84}
85
86impl Default for StorageSettings {
87    #[inline]
88    fn default() -> Self {
89        Self::new(
90            PathBuf::from("./chie-data"),
91            50 * 1024 * 1024 * 1024, // 50 GB
92        )
93    }
94}
95
96/// Network configuration settings.
97#[derive(Debug, Clone)]
98pub struct NetworkSettings {
99    /// Maximum bandwidth in bytes per second.
100    pub max_bandwidth_bps: u64,
101    /// Maximum concurrent connections.
102    pub max_connections: usize,
103    /// Connection timeout in seconds.
104    pub connection_timeout_secs: u64,
105    /// Request timeout in seconds.
106    pub request_timeout_secs: u64,
107    /// Enable rate limiting.
108    pub enable_rate_limiting: bool,
109    /// Rate limit (requests per second).
110    pub rate_limit_rps: f64,
111}
112
113impl NetworkSettings {
114    /// Create network settings with specified bandwidth.
115    #[inline]
116    #[must_use]
117    pub fn new(max_bandwidth_bps: u64) -> Self {
118        Self {
119            max_bandwidth_bps,
120            max_connections: 100,
121            connection_timeout_secs: 10,
122            request_timeout_secs: 30,
123            enable_rate_limiting: true,
124            rate_limit_rps: 100.0,
125        }
126    }
127
128    /// Get max bandwidth in Mbps.
129    #[inline]
130    #[must_use]
131    pub const fn max_bandwidth_mbps(&self) -> f64 {
132        (self.max_bandwidth_bps * 8) as f64 / (1024.0 * 1024.0)
133    }
134
135    /// Validate network settings.
136    pub fn validate(&self) -> Result<(), ConfigError> {
137        if self.max_bandwidth_bps == 0 {
138            return Err(ConfigError::InvalidValue(
139                "max_bandwidth_bps must be greater than 0".into(),
140            ));
141        }
142
143        if self.max_connections == 0 {
144            return Err(ConfigError::InvalidValue(
145                "max_connections must be greater than 0".into(),
146            ));
147        }
148
149        if self.rate_limit_rps < 0.0 {
150            return Err(ConfigError::InvalidValue(
151                "rate_limit_rps must be non-negative".into(),
152            ));
153        }
154
155        Ok(())
156    }
157}
158
159impl Default for NetworkSettings {
160    #[inline]
161    fn default() -> Self {
162        Self::new(100 * 1024 * 1024 / 8) // 100 Mbps
163    }
164}
165
166/// Coordinator configuration settings.
167#[derive(Debug, Clone)]
168pub struct CoordinatorSettings {
169    /// Coordinator base URL.
170    pub url: String,
171    /// API key for authentication.
172    pub api_key: Option<String>,
173    /// Proof submission interval in seconds.
174    pub proof_submit_interval_secs: u64,
175    /// Batch size for proof submissions.
176    pub proof_batch_size: usize,
177    /// Enable automatic proof submission.
178    pub auto_submit: bool,
179}
180
181impl CoordinatorSettings {
182    /// Create coordinator settings with specified URL.
183    #[inline]
184    #[must_use]
185    pub fn new(url: String) -> Self {
186        Self {
187            url,
188            api_key: None,
189            proof_submit_interval_secs: 60,
190            proof_batch_size: 10,
191            auto_submit: true,
192        }
193    }
194
195    /// Validate coordinator settings.
196    pub fn validate(&self) -> Result<(), ConfigError> {
197        if self.url.is_empty() {
198            return Err(ConfigError::InvalidValue(
199                "Coordinator URL cannot be empty".into(),
200            ));
201        }
202
203        if !self.url.starts_with("http://") && !self.url.starts_with("https://") {
204            return Err(ConfigError::InvalidValue(
205                "Coordinator URL must start with http:// or https://".into(),
206            ));
207        }
208
209        if self.proof_batch_size == 0 {
210            return Err(ConfigError::InvalidValue(
211                "proof_batch_size must be greater than 0".into(),
212            ));
213        }
214
215        Ok(())
216    }
217}
218
219impl Default for CoordinatorSettings {
220    #[inline]
221    fn default() -> Self {
222        Self::new("https://coordinator.chie.network".to_string())
223    }
224}
225
226/// Performance tuning settings.
227#[derive(Debug, Clone)]
228pub struct PerformanceSettings {
229    /// Enable chunk prefetching.
230    pub enable_prefetch: bool,
231    /// Prefetch cache size (number of chunks).
232    pub prefetch_cache_size: usize,
233    /// Enable content compression.
234    pub enable_compression: bool,
235    /// Enable content deduplication.
236    pub enable_deduplication: bool,
237    /// Garbage collection interval in seconds.
238    pub gc_interval_secs: u64,
239    /// Enable performance profiling.
240    pub enable_profiling: bool,
241}
242
243impl PerformanceSettings {
244    /// Validate performance settings.
245    pub fn validate(&self) -> Result<(), ConfigError> {
246        if self.prefetch_cache_size == 0 {
247            return Err(ConfigError::InvalidValue(
248                "prefetch_cache_size must be greater than 0".into(),
249            ));
250        }
251
252        Ok(())
253    }
254}
255
256impl Default for PerformanceSettings {
257    #[inline]
258    fn default() -> Self {
259        Self {
260            enable_prefetch: true,
261            prefetch_cache_size: 100,
262            enable_compression: true,
263            enable_deduplication: true,
264            gc_interval_secs: 3600, // 1 hour
265            enable_profiling: false,
266        }
267    }
268}
269
270/// Complete node settings.
271#[derive(Debug, Clone, Default)]
272pub struct NodeSettings {
273    /// Storage configuration.
274    pub storage: StorageSettings,
275    /// Network configuration.
276    pub network: NetworkSettings,
277    /// Coordinator configuration.
278    pub coordinator: CoordinatorSettings,
279    /// Performance configuration.
280    pub performance: PerformanceSettings,
281}
282
283impl NodeSettings {
284    /// Create a builder for node settings.
285    #[inline]
286    #[must_use]
287    pub fn builder() -> NodeSettingsBuilder {
288        NodeSettingsBuilder::default()
289    }
290
291    /// Validate all settings.
292    pub fn validate(&self) -> Result<(), ConfigError> {
293        self.storage.validate()?;
294        self.network.validate()?;
295        self.coordinator.validate()?;
296        self.performance.validate()?;
297        Ok(())
298    }
299
300    /// Load settings from environment variables.
301    pub fn from_env() -> Result<Self, ConfigError> {
302        let mut settings = Self::default();
303
304        // Storage
305        if let Ok(path) = std::env::var("CHIE_STORAGE_PATH") {
306            settings.storage.base_path = PathBuf::from(path);
307        }
308        if let Ok(max_bytes) = std::env::var("CHIE_STORAGE_MAX_BYTES") {
309            settings.storage.max_bytes = max_bytes
310                .parse()
311                .map_err(|_| ConfigError::InvalidValue("Invalid CHIE_STORAGE_MAX_BYTES".into()))?;
312        }
313
314        // Network
315        if let Ok(bandwidth) = std::env::var("CHIE_MAX_BANDWIDTH_BPS") {
316            settings.network.max_bandwidth_bps = bandwidth
317                .parse()
318                .map_err(|_| ConfigError::InvalidValue("Invalid CHIE_MAX_BANDWIDTH_BPS".into()))?;
319        }
320
321        // Coordinator
322        if let Ok(url) = std::env::var("CHIE_COORDINATOR_URL") {
323            settings.coordinator.url = url;
324        }
325        if let Ok(api_key) = std::env::var("CHIE_API_KEY") {
326            settings.coordinator.api_key = Some(api_key);
327        }
328
329        settings.validate()?;
330        Ok(settings)
331    }
332}
333
334/// Builder for node settings.
335#[derive(Debug, Default)]
336pub struct NodeSettingsBuilder {
337    storage: Option<StorageSettings>,
338    network: Option<NetworkSettings>,
339    coordinator: Option<CoordinatorSettings>,
340    performance: Option<PerformanceSettings>,
341}
342
343impl NodeSettingsBuilder {
344    /// Set storage settings.
345    #[inline]
346    #[must_use]
347    pub fn storage(mut self, storage: StorageSettings) -> Self {
348        self.storage = Some(storage);
349        self
350    }
351
352    /// Set network settings.
353    #[inline]
354    #[must_use]
355    pub fn network(mut self, network: NetworkSettings) -> Self {
356        self.network = Some(network);
357        self
358    }
359
360    /// Set coordinator settings.
361    #[inline]
362    #[must_use]
363    pub fn coordinator(mut self, coordinator: CoordinatorSettings) -> Self {
364        self.coordinator = Some(coordinator);
365        self
366    }
367
368    /// Set performance settings.
369    #[inline]
370    #[must_use]
371    pub fn performance(mut self, performance: PerformanceSettings) -> Self {
372        self.performance = Some(performance);
373        self
374    }
375
376    /// Build the node settings.
377    pub fn build(self) -> Result<NodeSettings, ConfigError> {
378        let settings = NodeSettings {
379            storage: self.storage.unwrap_or_default(),
380            network: self.network.unwrap_or_default(),
381            coordinator: self.coordinator.unwrap_or_default(),
382            performance: self.performance.unwrap_or_default(),
383        };
384
385        settings.validate()?;
386        Ok(settings)
387    }
388}
389
390/// Configuration errors.
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub enum ConfigError {
393    /// Invalid configuration value.
394    InvalidValue(String),
395    /// Missing required field.
396    MissingField(String),
397}
398
399impl std::fmt::Display for ConfigError {
400    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401        match self {
402            Self::InvalidValue(msg) => write!(f, "Invalid configuration value: {}", msg),
403            Self::MissingField(field) => write!(f, "Missing required field: {}", field),
404        }
405    }
406}
407
408impl std::error::Error for ConfigError {}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    #[test]
415    fn test_storage_settings_default() {
416        let settings = StorageSettings::default();
417        assert_eq!(settings.max_bytes, 50 * 1024 * 1024 * 1024);
418        assert!(settings.validate().is_ok());
419    }
420
421    #[test]
422    fn test_storage_settings_validation() {
423        let settings = StorageSettings {
424            max_bytes: 0,
425            ..Default::default()
426        };
427        assert!(settings.validate().is_err());
428
429        let settings = StorageSettings {
430            min_free_bytes: 50 * 1024 * 1024 * 1024 + 1,
431            ..Default::default()
432        };
433        assert!(settings.validate().is_err());
434    }
435
436    #[test]
437    fn test_network_settings_default() {
438        let settings = NetworkSettings::default();
439        assert_eq!(settings.max_connections, 100);
440        assert!(settings.validate().is_ok());
441    }
442
443    #[test]
444    fn test_network_settings_mbps() {
445        let settings = NetworkSettings::new(100 * 1024 * 1024 / 8);
446        assert_eq!(settings.max_bandwidth_mbps(), 100.0);
447    }
448
449    #[test]
450    fn test_coordinator_settings_validation() {
451        let settings = CoordinatorSettings {
452            url: String::new(),
453            ..Default::default()
454        };
455        assert!(settings.validate().is_err());
456
457        let settings = CoordinatorSettings {
458            url: "invalid-url".to_string(),
459            ..Default::default()
460        };
461        assert!(settings.validate().is_err());
462    }
463
464    #[test]
465    fn test_node_settings_builder() {
466        let settings = NodeSettings::builder()
467            .storage(StorageSettings::default())
468            .network(NetworkSettings::default())
469            .build();
470
471        assert!(settings.is_ok());
472    }
473
474    #[test]
475    fn test_node_settings_default() {
476        let settings = NodeSettings::default();
477        assert!(settings.validate().is_ok());
478    }
479
480    #[test]
481    fn test_performance_settings_default() {
482        let settings = PerformanceSettings::default();
483        assert!(settings.enable_prefetch);
484        assert!(settings.validate().is_ok());
485    }
486
487    #[test]
488    fn test_config_error_display() {
489        let err = ConfigError::InvalidValue("test".to_string());
490        assert_eq!(err.to_string(), "Invalid configuration value: test");
491
492        let err = ConfigError::MissingField("field1".to_string());
493        assert_eq!(err.to_string(), "Missing required field: field1");
494    }
495
496    #[test]
497    fn test_storage_tiering_validation() {
498        let settings = StorageSettings {
499            enable_tiering: true,
500            ..Default::default()
501        };
502        assert!(settings.validate().is_err());
503
504        let mut settings = settings;
505        settings.ssd_path = Some(PathBuf::from("/ssd"));
506        settings.hdd_path = Some(PathBuf::from("/hdd"));
507        assert!(settings.validate().is_ok());
508    }
509}