d_engine/config/
mod.rs

1//! Configuration management module for distributed Raft consensus engine.
2//!
3//! Provides hierarchical configuration loading and validation with:
4//! - Default values as code base
5//! - Environment variable overrides
6//! - Configuration file support
7//! - Component-wise validation
8mod cluster;
9use std::fmt::Debug;
10use std::path::Path;
11mod network;
12mod raft;
13mod retry;
14mod tls;
15pub use cluster::*;
16use config::ConfigError;
17pub use network::*;
18pub use raft::*;
19pub use retry::*;
20pub use tls::*;
21#[cfg(test)]
22mod config_test;
23#[cfg(test)]
24mod raft_test;
25use std::env;
26
27use config::Config;
28use config::Environment;
29use config::File;
30use serde::Deserialize;
31use serde::Serialize;
32
33use crate::proto::common::NodeStatus;
34use crate::Error;
35use crate::Result;
36
37/// Main configuration container for Raft consensus engine components
38///
39/// Combines all subsystem configurations with hierarchical override support:
40/// 1. Default values from code implementation
41/// 2. Configuration file specified by `CONFIG_PATH`
42/// 3. Environment variables (highest priority)
43#[derive(Serialize, Deserialize, Clone, Default)]
44pub struct RaftNodeConfig {
45    /// Cluster topology and node configuration
46    pub cluster: ClusterConfig,
47    /// Network communication parameters
48    pub network: NetworkConfig,
49    /// Core Raft algorithm parameters
50    pub raft: RaftConfig,
51    /// Retry policies for distributed operations
52    pub retry: RetryPolicies,
53    /// TLS/SSL security configuration
54    pub tls: TlsConfig,
55}
56impl Debug for RaftNodeConfig {
57    fn fmt(
58        &self,
59        f: &mut std::fmt::Formatter<'_>,
60    ) -> std::fmt::Result {
61        f.debug_struct("RaftNodeConfig").field("cluster", &self.cluster).finish()
62    }
63}
64impl RaftNodeConfig {
65    /// Creates a new configuration with hierarchical override support:
66    ///
67    /// Configuration sources are merged in the following order (later sources
68    /// override earlier ones):
69    /// 1. Type defaults (lowest priority)
70    /// 2. Configuration file from `CONFIG_PATH` environment variable
71    /// 3. Environment variables with `RAFT__` prefix (highest priority)
72    ///
73    /// # Returns
74    /// Merged configuration instance or error if:
75    /// - Config file parsing fails
76    /// - Validation rules are violated
77    ///
78    /// # Example
79    /// ```ignore
80    /// // Load with default values only
81    /// let cfg = RaftNodeConfig::new()?;
82    ///
83    /// // Load with config file and environment variables
84    /// std::env::set_var("CONFIG_PATH", "config/cluster.toml");
85    /// std::env::set_var("RAFT__CLUSTER__NODE_ID", "100");
86    /// let cfg = RaftNodeConfig::new()?;
87    /// ```
88    pub fn new() -> Result<Self> {
89        // Create a basic configuration builder
90        // 1. Default values ​​as the base layer
91        let mut builder = Config::builder().add_source(Config::try_from(&Self::default())?);
92
93        // 2. Conditionally add configuration files
94        if let Ok(config_path) = env::var("CONFIG_PATH") {
95            builder = builder.add_source(File::with_name(&config_path));
96        }
97
98        // 3. Add environment variable source
99        builder = builder.add_source(
100            Environment::with_prefix("RAFT")
101                .separator("__")
102                .ignore_empty(true)
103                .try_parsing(true),
104        );
105
106        // Build and deserialize
107        let config: Self = builder.build()?.try_deserialize()?;
108        config.validate()?;
109        Ok(config)
110    }
111
112    /// Creates a new configuration with additional overrides:
113    ///
114    /// Merging order (later sources override earlier ones):
115    /// 1. Current configuration values
116    /// 2. New configuration file
117    /// 3. Latest environment variables (highest priority)
118    ///
119    /// # Example
120    /// ```ignore
121    /// // Initial configuration
122    /// let base = RaftNodeConfig::new()?;
123    ///
124    /// // Apply runtime overrides
125    /// let final_cfg = base.with_override_config("runtime_overrides.toml")?;
126    /// ```
127    pub fn with_override_config(
128        &self,
129        path: &str,
130    ) -> Result<Self> {
131        let config: Self = Config::builder()
132            .add_source(Config::try_from(self)?) // Current config
133            .add_source(File::with_name(path)) // New overrides
134            .add_source(
135                // Fresh environment
136                Environment::with_prefix("RAFT")
137                    .separator("__")
138                    .ignore_empty(true)
139                    .try_parsing(true),
140            )
141            .build()?
142            .try_deserialize()?;
143        config.validate()?;
144        Ok(config)
145    }
146
147    /// Validates cross-component configuration rules
148    ///
149    /// # Returns
150    /// `Ok(())` if all configurations are valid, or
151    /// `Err(Error)` containing validation failure details
152    ///
153    /// # Errors
154    /// Returns validation errors from any subsystem:
155    /// - Invalid port bindings
156    /// - Conflicting node IDs
157    /// - Expired certificates
158    /// - Retry policy conflicts
159    pub fn validate(&self) -> Result<()> {
160        self.cluster.validate()?;
161        self.raft.validate()?;
162        self.network.validate()?;
163        self.tls.validate()?;
164        self.retry.validate()?;
165        Ok(())
166    }
167
168    pub fn is_joining(&self) -> bool {
169        self.cluster
170            .initial_cluster
171            .iter()
172            .find(|n| n.id == self.cluster.node_id)
173            .map(|n| n.status == NodeStatus::Joining as i32)
174            .unwrap_or(false)
175    }
176}
177
178/// Ensures directory path is valid and writable
179pub(super) fn validate_directory(
180    path: &Path,
181    name: &str,
182) -> Result<()> {
183    if path.as_os_str().is_empty() {
184        return Err(Error::Config(ConfigError::Message(format!(
185            "{name} path cannot be empty"
186        ))));
187    }
188
189    #[cfg(not(test))]
190    {
191        use std::fs;
192        // Check directory existence or create ability
193        if !path.exists() {
194            fs::create_dir_all(path).map_err(|e| {
195                Error::Config(ConfigError::Message(format!(
196                    "Failed to create {} directory at {}: {}",
197                    name,
198                    path.display(),
199                    e
200                )))
201            })?;
202        }
203
204        // Check write permissions
205        let test_file = path.join(".permission_test");
206        fs::write(&test_file, b"test").map_err(|e| {
207            Error::Config(ConfigError::Message(format!(
208                "No write permission in {} directory {}: {}",
209                name,
210                path.display(),
211                e
212            )))
213        })?;
214        fs::remove_file(&test_file).ok();
215    }
216
217    Ok(())
218}