batata_consul_client/
config.rs

1use std::time::Duration;
2
3use crate::error::{ConsulError, Result};
4
5/// Default Consul HTTP port
6pub const DEFAULT_HTTP_PORT: u16 = 8500;
7/// Default Consul address
8pub const DEFAULT_ADDRESS: &str = "127.0.0.1:8500";
9/// Default HTTP scheme
10pub const DEFAULT_SCHEME: &str = "http";
11
12/// TLS configuration for HTTPS connections
13#[derive(Clone, Debug, Default)]
14pub struct TlsConfig {
15    /// CA certificate path
16    pub ca_cert: Option<String>,
17    /// CA certificate PEM data
18    pub ca_cert_pem: Option<Vec<u8>>,
19    /// Client certificate path
20    pub client_cert: Option<String>,
21    /// Client certificate PEM data
22    pub client_cert_pem: Option<Vec<u8>>,
23    /// Client key path
24    pub client_key: Option<String>,
25    /// Client key PEM data
26    pub client_key_pem: Option<Vec<u8>>,
27    /// Skip server certificate verification (insecure)
28    pub insecure_skip_verify: bool,
29}
30
31impl TlsConfig {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn with_ca_cert(mut self, path: &str) -> Self {
37        self.ca_cert = Some(path.to_string());
38        self
39    }
40
41    pub fn with_client_cert(mut self, cert_path: &str, key_path: &str) -> Self {
42        self.client_cert = Some(cert_path.to_string());
43        self.client_key = Some(key_path.to_string());
44        self
45    }
46
47    pub fn with_insecure_skip_verify(mut self) -> Self {
48        self.insecure_skip_verify = true;
49        self
50    }
51}
52
53/// HTTP basic authentication
54#[derive(Clone, Debug)]
55pub struct HttpBasicAuth {
56    /// Username
57    pub username: String,
58    /// Password
59    pub password: String,
60}
61
62impl HttpBasicAuth {
63    pub fn new(username: &str, password: &str) -> Self {
64        Self {
65            username: username.to_string(),
66            password: password.to_string(),
67        }
68    }
69}
70
71/// Client configuration
72#[derive(Clone, Debug)]
73pub struct Config {
74    /// Consul server address (host:port)
75    pub address: String,
76    /// HTTP scheme (http or https)
77    pub scheme: String,
78    /// Datacenter to use
79    pub datacenter: Option<String>,
80    /// HTTP basic authentication
81    pub http_auth: Option<HttpBasicAuth>,
82    /// ACL token
83    pub token: Option<String>,
84    /// Token file path
85    pub token_file: Option<String>,
86    /// TLS configuration
87    pub tls_config: Option<TlsConfig>,
88    /// Request timeout
89    pub timeout: Duration,
90    /// Namespace (Enterprise only)
91    pub namespace: Option<String>,
92    /// Partition (Enterprise only)
93    pub partition: Option<String>,
94}
95
96impl Default for Config {
97    fn default() -> Self {
98        Self {
99            address: DEFAULT_ADDRESS.to_string(),
100            scheme: DEFAULT_SCHEME.to_string(),
101            datacenter: None,
102            http_auth: None,
103            token: None,
104            token_file: None,
105            tls_config: None,
106            timeout: Duration::from_secs(30),
107            namespace: None,
108            partition: None,
109        }
110    }
111}
112
113impl Config {
114    /// Create a new default configuration
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Create configuration from environment variables
120    pub fn from_env() -> Result<Self> {
121        let mut config = Self::default();
122
123        if let Ok(addr) = std::env::var("CONSUL_HTTP_ADDR") {
124            // Parse address which may include scheme
125            if addr.starts_with("https://") {
126                config.scheme = "https".to_string();
127                config.address = addr.trim_start_matches("https://").to_string();
128            } else if addr.starts_with("http://") {
129                config.scheme = "http".to_string();
130                config.address = addr.trim_start_matches("http://").to_string();
131            } else {
132                config.address = addr;
133            }
134        }
135
136        if let Ok(token) = std::env::var("CONSUL_HTTP_TOKEN") {
137            config.token = Some(token);
138        }
139
140        if let Ok(token_file) = std::env::var("CONSUL_HTTP_TOKEN_FILE") {
141            config.token_file = Some(token_file);
142        }
143
144        if let Ok(auth) = std::env::var("CONSUL_HTTP_AUTH") {
145            if let Some((username, password)) = auth.split_once(':') {
146                config.http_auth = Some(HttpBasicAuth::new(username, password));
147            }
148        }
149
150        if let Ok(ssl) = std::env::var("CONSUL_HTTP_SSL") {
151            if ssl == "true" || ssl == "1" {
152                config.scheme = "https".to_string();
153            }
154        }
155
156        if let Ok(verify) = std::env::var("CONSUL_HTTP_SSL_VERIFY") {
157            if verify == "false" || verify == "0" {
158                let mut tls = config.tls_config.unwrap_or_default();
159                tls.insecure_skip_verify = true;
160                config.tls_config = Some(tls);
161            }
162        }
163
164        if let Ok(ca_cert) = std::env::var("CONSUL_CACERT") {
165            let mut tls = config.tls_config.unwrap_or_default();
166            tls.ca_cert = Some(ca_cert);
167            config.tls_config = Some(tls);
168        }
169
170        if let Ok(client_cert) = std::env::var("CONSUL_CLIENT_CERT") {
171            let mut tls = config.tls_config.unwrap_or_default();
172            tls.client_cert = Some(client_cert);
173            config.tls_config = Some(tls);
174        }
175
176        if let Ok(client_key) = std::env::var("CONSUL_CLIENT_KEY") {
177            let mut tls = config.tls_config.unwrap_or_default();
178            tls.client_key = Some(client_key);
179            config.tls_config = Some(tls);
180        }
181
182        if let Ok(ns) = std::env::var("CONSUL_NAMESPACE") {
183            config.namespace = Some(ns);
184        }
185
186        if let Ok(partition) = std::env::var("CONSUL_PARTITION") {
187            config.partition = Some(partition);
188        }
189
190        Ok(config)
191    }
192
193    /// Get the base URL for API requests
194    pub fn base_url(&self) -> String {
195        format!("{}://{}", self.scheme, self.address)
196    }
197
198    /// Validate the configuration
199    pub fn validate(&self) -> Result<()> {
200        if self.address.is_empty() {
201            return Err(ConsulError::InvalidConfig("address is required".to_string()));
202        }
203
204        if self.scheme != "http" && self.scheme != "https" {
205            return Err(ConsulError::InvalidConfig(format!(
206                "invalid scheme: {}",
207                self.scheme
208            )));
209        }
210
211        Ok(())
212    }
213}
214
215/// Builder for Config
216pub struct ConfigBuilder {
217    config: Config,
218}
219
220impl ConfigBuilder {
221    pub fn new() -> Self {
222        Self {
223            config: Config::default(),
224        }
225    }
226
227    pub fn address(mut self, address: &str) -> Self {
228        self.config.address = address.to_string();
229        self
230    }
231
232    pub fn scheme(mut self, scheme: &str) -> Self {
233        self.config.scheme = scheme.to_string();
234        self
235    }
236
237    pub fn datacenter(mut self, dc: &str) -> Self {
238        self.config.datacenter = Some(dc.to_string());
239        self
240    }
241
242    pub fn token(mut self, token: &str) -> Self {
243        self.config.token = Some(token.to_string());
244        self
245    }
246
247    pub fn http_auth(mut self, username: &str, password: &str) -> Self {
248        self.config.http_auth = Some(HttpBasicAuth::new(username, password));
249        self
250    }
251
252    pub fn tls_config(mut self, tls: TlsConfig) -> Self {
253        self.config.tls_config = Some(tls);
254        self
255    }
256
257    pub fn timeout(mut self, timeout: Duration) -> Self {
258        self.config.timeout = timeout;
259        self
260    }
261
262    pub fn namespace(mut self, ns: &str) -> Self {
263        self.config.namespace = Some(ns.to_string());
264        self
265    }
266
267    pub fn partition(mut self, partition: &str) -> Self {
268        self.config.partition = Some(partition.to_string());
269        self
270    }
271
272    pub fn build(self) -> Result<Config> {
273        self.config.validate()?;
274        Ok(self.config)
275    }
276}
277
278impl Default for ConfigBuilder {
279    fn default() -> Self {
280        Self::new()
281    }
282}