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}