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}