irx_config/config.rs
1//! This module define main configuration structures: [`Config`] and [`ConfigBuilder`].
2
3use crate::{AnyParser, Error, MergeCase, Parse, Result, Value, DEFAULT_KEYS_SEPARATOR};
4use serde::de::DeserializeOwned;
5use std::cmp::Ordering;
6use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
7
8cfg_if::cfg_if! {
9 if #[cfg(feature = "blake2b")] {
10 use blake2b_simd as HashModule;
11 use HashModule::blake2b as hash_func;
12 const HASH_NAME: &str = "BLAKE2b";
13 } else if #[cfg(feature = "blake3")] {
14 use blake3 as HashModule;
15 use HashModule::hash as hash_func;
16 const HASH_NAME: &str = "BLAKE3";
17 }
18}
19
20#[derive(PartialEq)]
21struct Hash(HashModule::Hash);
22
23impl Hash {
24 #[inline]
25 fn as_bytes(&self) -> &[u8] {
26 self.0.as_bytes()
27 }
28}
29
30impl From<&[u8]> for Hash {
31 #[inline]
32 fn from(value: &[u8]) -> Self {
33 Self(hash_func(value))
34 }
35}
36
37impl Debug for Hash {
38 #[inline]
39 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
40 Debug::fmt(&self.0, f)
41 }
42}
43
44impl Display for Hash {
45 #[inline]
46 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
47 f.write_str(&self.0.to_hex())
48 }
49}
50
51/// Container for all parser sources which will (re)load data from a parsers in order in which they was added
52/// to [`ConfigBuilder`]. It will provide access to merged set of (re)loaded configuration parameters.
53pub struct Config {
54 parsers: Vec<AnyParser>,
55 value: Value,
56 case_on: bool,
57 hash: Hash,
58 sealed_suffix: String,
59 keys_delimiter: String,
60}
61
62impl Config {
63 /// Reload and re-merge all configuration data from parsers.
64 ///
65 /// # Errors
66 ///
67 /// If any errors will occur during parsing/merging then error will be returned.
68 pub fn reload(&mut self) -> Result<&mut Self> {
69 let mut value = Value::default();
70 for (idx, parser) in self.parsers.iter_mut().enumerate() {
71 value = parser
72 .parse(&value)
73 .map_err(|e| Error::ParseValue(e, idx + 1))?
74 .merge_with_case(&value, self.case_on);
75 }
76
77 value.seal(&self.sealed_suffix);
78 self.hash = Hash::from(value.as_bytes().as_ref());
79 self.value = value;
80 Ok(self)
81 }
82
83 /// Name of the hash used for loaded configuration data.
84 #[inline]
85 pub fn hash_name() -> &'static str {
86 HASH_NAME
87 }
88
89 /// Calculate hash for currently loaded configuration data.
90 #[inline]
91 pub fn hash(&self) -> String {
92 [HASH_NAME, ": ", &self.hash.to_string()].concat()
93 }
94
95 /// Returns configuration data value to corresponding key/nested keys.
96 ///
97 /// # Example
98 ///
99 /// ```
100 /// let name: Option<u32> = conf.get_by_keys(["logger", "name"])?;
101 /// ```
102 ///
103 /// # Errors
104 ///
105 /// If keys is empty, the error will be returned.
106 #[inline]
107 pub fn get_by_keys<I, K, T>(&self, keys: I) -> Result<Option<T>>
108 where
109 I: IntoIterator<Item = K>,
110 K: AsRef<str>,
111 T: DeserializeOwned,
112 {
113 self.value.get_by_keys(keys)
114 }
115
116 /// Returns configuration data value to corresponding key path with keys delimiter. Default delimiter is
117 /// [`DEFAULT_KEYS_SEPARATOR`].
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// use serde::Deserialize;
123 ///
124 /// #[derive(Deserialize)]
125 /// struct Person {
126 /// first_name: String,
127 /// last_name: String,
128 /// age: u8,
129 /// }
130 ///
131 /// let person: Option<Person> = conf.get_by_key_path("contact:info")?;
132 /// ```
133 ///
134 /// # Errors
135 ///
136 /// If keys path or keys delimiter is empty, the corresponding error will be returned.
137 #[inline]
138 pub fn get_by_key_path<T, P>(&self, path: P) -> Result<Option<T>>
139 where
140 T: DeserializeOwned,
141 P: AsRef<str>,
142 {
143 self.value
144 .get_by_key_path_with_delim(path, &self.keys_delimiter)
145 }
146
147 /// Returns configuration data value to corresponding key path with delimiter.
148 ///
149 /// # Example
150 ///
151 /// ```
152 /// let name: Option<u32> = conf.get_by_key_path_with_delim("logger:name", ":")?;
153 /// ```
154 ///
155 /// # Errors
156 ///
157 /// If keys path or delimiter is empty, the corresponding error will be returned.
158 #[inline]
159 pub fn get_by_key_path_with_delim<T, P, D>(&self, path: P, delim: D) -> Result<Option<T>>
160 where
161 T: DeserializeOwned,
162 P: AsRef<str>,
163 D: AsRef<str>,
164 {
165 self.value.get_by_key_path_with_delim(path, delim)
166 }
167
168 /// Deserialize configuration to destination struct/value.
169 ///
170 /// # Example
171 ///
172 /// ```
173 /// use serde::Deserialize;
174 ///
175 /// #[derive(Deserialize)]
176 /// struct Person {
177 /// first_name: String,
178 /// last_name: String,
179 /// age: u8,
180 /// }
181 ///
182 /// let person: Person = conf.get()?;
183 /// ```
184 ///
185 /// # Errors
186 ///
187 /// In case of any de-serialization problems the corresponding error will be returned.
188 #[inline]
189 pub fn get<T: DeserializeOwned>(&self) -> Result<T> {
190 self.value.get()
191 }
192
193 /// Get reference to internal [`Value`] structure.
194 #[inline]
195 pub fn get_value(&self) -> &Value {
196 &self.value
197 }
198}
199
200impl Debug for Config {
201 #[inline]
202 fn fmt(&self, f: &mut Formatter) -> FmtResult {
203 f.write_fmt(format_args!(
204 "Config {{ parsers: size({}), value: {:?}, case_on: {:?}, hash: {:?}, sealed_suffix: {:?}, keys_delimiter: {:?} }}",
205 self.parsers.len(),
206 self.value,
207 self.case_on,
208 self.hash,
209 self.sealed_suffix,
210 self.keys_delimiter,
211 ))
212 }
213}
214
215impl Display for Config {
216 #[inline]
217 fn fmt(&self, f: &mut Formatter) -> FmtResult {
218 f.write_fmt(format_args!("Config: {}\n{}", self.hash(), self.value))
219 }
220}
221
222impl PartialEq for Config {
223 #[inline]
224 fn eq(&self, other: &Self) -> bool {
225 self.hash == other.hash
226 }
227}
228
229impl Eq for Config {}
230
231impl PartialOrd for Config {
232 #[inline]
233 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
234 Some(self.cmp(other))
235 }
236}
237
238impl Ord for Config {
239 #[inline]
240 fn cmp(&self, other: &Self) -> Ordering {
241 self.hash.as_bytes().cmp(other.hash.as_bytes())
242 }
243}
244
245/// The builder for [`Config`] structure.
246pub struct ConfigBuilder {
247 parsers: Vec<AnyParser>,
248 sealed_suffix: String,
249 keys_delimiter: String,
250 auto_case_on: bool,
251 merge_case: MergeCase,
252}
253
254impl ConfigBuilder {
255 /// Append a parser to [`Config`]. First appended parser will have highest priority during (re)load merge, the last
256 /// one will have lowest priority.
257 ///
258 /// # Example
259 ///
260 /// ```
261 /// use irx_config::parsers::{env, json};
262 /// use irx_config::ConfigBuilder;
263 ///
264 /// let config = ConfigBuilder::default()
265 /// .append_parser(
266 /// json::ParserBuilder::default()
267 /// .default_path("config.json")
268 /// .build()?,
269 /// )
270 /// .append_parser(
271 /// env::ParserBuilder::default()
272 /// .default_prefix("APP_")
273 /// .build()?,
274 /// )
275 /// .load()?;
276 /// ```
277 #[inline]
278 pub fn append_parser<P>(mut self, parser: P) -> Self
279 where
280 P: Parse + 'static,
281 {
282 self.auto_case_on = self.auto_case_on && parser.is_case_sensitive();
283 self.parsers.push(Box::new(parser));
284 self
285 }
286
287 /// Set suffix for keys to mark them as a secret value which will be obfuscated during display/debugging output.
288 /// If not set then all values will be displayed as is.
289 ///
290 ///
291 /// # Example
292 ///
293 /// ```
294 /// use irx_config::parsers::env;
295 /// use irx_config::ConfigBuilder;
296 ///
297 /// let config = ConfigBuilder::default()
298 /// .append_parser(
299 /// env::ParserBuilder::default()
300 /// .default_prefix("APP_")
301 /// .build()?,
302 /// )
303 /// .sealed_suffix("_sealed_")
304 /// .load()?;
305 /// ```
306 #[inline]
307 pub fn sealed_suffix<S>(mut self, suffix: S) -> Self
308 where
309 S: Into<String>,
310 {
311 self.sealed_suffix = suffix.into();
312 self
313 }
314
315 /// Set default key level delimiter. Default is [`DEFAULT_KEYS_SEPARATOR`].
316 ///
317 /// # Example
318 ///
319 /// ```
320 /// use clap::app_from_crate;
321 /// use irx_config::parsers::cmd;
322 /// use irx_config::ConfigBuilder;
323 ///
324 /// let app = app_from_crate!();
325 ///
326 /// let config = ConfigBuilder::default()
327 /// .append_parser(cmd::ParserBuilder::new(app).build()?)
328 /// .keys_delimiter("/")
329 /// .load()?;
330 /// ```
331 #[inline]
332 pub fn keys_delimiter<D>(mut self, delim: D) -> Self
333 where
334 D: Into<String>,
335 {
336 self.keys_delimiter = delim.into();
337 self
338 }
339
340 /// Set merge case mode for a keys (see [`MergeCase`]). Default is [`MergeCase::Auto`].
341 #[inline]
342 pub fn merge_case(mut self, case: MergeCase) -> Self {
343 self.merge_case = case;
344 self
345 }
346
347 /// Load all data from all previously appended parsers, merge data according to appended order and return [`Config`].
348 ///
349 /// # Errors
350 ///
351 /// If any errors will occur during parsing/merging then error will be returned.
352 pub fn load(self) -> Result<Config> {
353 let value = Value::default();
354 let hash = Hash::from(value.as_bytes().as_ref());
355 let case_on = if MergeCase::Auto == self.merge_case {
356 self.auto_case_on
357 } else {
358 MergeCase::Sensitive == self.merge_case
359 };
360
361 let mut config = Config {
362 parsers: self.parsers,
363 value,
364 case_on,
365 hash,
366 sealed_suffix: self.sealed_suffix,
367 keys_delimiter: self.keys_delimiter,
368 };
369 config.reload()?;
370 Ok(config)
371 }
372
373 /// Load data from one parser and return [`Config`].
374 ///
375 /// # Errors
376 ///
377 /// If any errors will occur during parsing/merging then error will be returned.
378 #[inline]
379 pub fn load_one<P>(parser: P) -> Result<Config>
380 where
381 P: Parse + 'static,
382 {
383 ConfigBuilder::default().append_parser(parser).load()
384 }
385
386 /// Load all data from parsers' iterator, merge data according to iterator order and return [`Config`].
387 ///
388 /// # Errors
389 ///
390 /// If any errors will occur during parsing/merging then error will be returned.
391 #[inline]
392 pub fn load_from<I, P>(parsers: I) -> Result<Config>
393 where
394 I: IntoIterator<Item = P>,
395 P: Parse + 'static,
396 {
397 parsers
398 .into_iter()
399 .fold(ConfigBuilder::default(), |s, p| s.append_parser(p))
400 .load()
401 }
402}
403
404impl Default for ConfigBuilder {
405 fn default() -> Self {
406 Self {
407 parsers: Default::default(),
408 sealed_suffix: Default::default(),
409 keys_delimiter: DEFAULT_KEYS_SEPARATOR.to_string(),
410 auto_case_on: true,
411 merge_case: Default::default(),
412 }
413 }
414}