Skip to main content

irx_config/
value.rs

1//! This module define [`Value`] structure which represent key-value based configuration data.
2
3use crate::{Error, Result, DEFAULT_KEYS_SEPARATOR};
4use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
5pub use serde_json::json;
6pub(super) use serde_json::Error as SerdeError;
7use serde_json::{map::Map, Value as InnerValue};
8use std::{
9    borrow::Cow,
10    fmt::{Debug, Display, Error as FmtError, Formatter, Result as FmtResult},
11    result::Result as StdResult,
12};
13
14type ValueMap = Map<String, InnerValue>;
15type CowInnerValue<'a> = Cow<'a, InnerValue>;
16
17/// The sealed states for [`Value`] structure.
18///
19/// If [`Value`] is sealed, the sensitive fields values will be obfuscated with `********` during display/debugging output.
20///
21/// **IMPORTANT:** Once [`Value`] was sealed, but fully/partially mutated after that, it will be represented as empty
22/// dictionary during display/debugging output, to prevent sensitive data leakages.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub enum SealedState {
25    /// A [`Value`] was never sealed, all data will be represented as is during display/debugging output.
26    #[default]
27    None,
28    /// A [`Value`] was sealed, sensitive data will be obfuscated during display/debugging output.
29    On,
30    /// A [`Value`] was sealed and fully/partially mutated after that, whole [`Value`] will be represented as empty
31    /// dictionary during display/debugging output (see above).
32    Mutated,
33}
34
35/// This structure represent key-value based configuration data.
36///
37/// **IMPORTANT:** All functionality related to the sealed state only affects the display/debugging output.
38#[derive(Clone)]
39pub struct Value {
40    value: InnerValue,
41    sealed: Option<InnerValue>,
42    sealed_state: SealedState,
43    case_on: bool,
44}
45
46impl Value {
47    /// Try to create [`Value`] structure from any type which implements [`Serialize`] trait and make key names to be
48    /// case-sensitive. If successful return instance of [`Value`] structure.
49    ///
50    /// # Errors
51    ///
52    /// If any errors will occur during construction then error will be returned.
53    ///
54    /// # Example
55    ///
56    /// ```
57    /// use irx_config::{json, Value};
58    ///
59    /// let data = Value::try_from(json!(
60    /// {
61    ///     "name": "John Doe",
62    ///     "age": 43,
63    ///     "phones": [
64    ///         "+44 1234567",
65    ///         "+44 2345678"
66    ///     ]
67    /// }))?;
68    /// ```
69    #[inline]
70    pub fn try_from<T: Serialize>(value: T) -> Result<Self> {
71        Self::try_from_with_case(value, true)
72    }
73
74    /// Try to create [`Value`] structure from any type which implements [`Serialize`] trait and make key names to be
75    /// case-sensitive/insensitive. If successful return instance of [`Value`] structure.
76    ///
77    /// # Errors
78    ///
79    /// If any errors will occur during construction then error will be returned.
80    #[inline]
81    pub fn try_from_with_case<T: Serialize>(value: T, case_on: bool) -> Result<Self> {
82        Ok(Self {
83            value: set(value, case_on)?,
84            case_on,
85            ..Default::default()
86        })
87    }
88
89    /// Create default [`Value`] structure with key names to be case-sensitive/insensitive.
90    #[inline]
91    pub fn with_case(on: bool) -> Self {
92        Self {
93            case_on: on,
94            ..Default::default()
95        }
96    }
97
98    /// Return `true` if key names is case-sensitive, otherwise return `false`.
99    #[inline]
100    pub fn is_case_sensitive(&self) -> bool {
101        self.case_on
102    }
103
104    /// Merge a input [`Value`] to the given [`Value`] structure. The key names will use case-sensitivity of given
105    /// [`Value`] during merge. Return merged result [`Value`] structure. If given [`Value`] was sealed and merge
106    /// operation was mutating then it will be in [`SealedState::Mutated`].
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use irx_config::{json, Value};
112    ///
113    /// let mut person = Value::try_from(json!({
114    ///     "name": "John Doe",
115    ///     "age": 43
116    /// }))?;
117    ///
118    /// let phones = Value::try_from(json!({
119    ///     "phones": [
120    ///         "+44 1234567",
121    ///         "+44 2345678"
122    ///     ]
123    /// }))?;
124    ///
125    /// person = person.merge(&phones);
126    /// ```
127    #[inline]
128    pub fn merge(self, value: &Value) -> Self {
129        let case_on = self.case_on;
130        self.merge_with_case(value, case_on)
131    }
132
133    /// Merge a input [`Value`] to the given [`Value`] structure. The key names will be case-sensitive or
134    /// case-insensitive during merge, according to `case_on` parameter. Return merged result [`Value`] structure.
135    /// If given [`Value`] was sealed and merge operation was mutating then it will be in [`SealedState::Mutated`].
136    pub fn merge_with_case(mut self, value: &Value, case_on: bool) -> Self {
137        let mut is_changed = self.normalize_case(case_on);
138        self.value = match self.value {
139            InnerValue::Object(dst) if value.value.is_object() => {
140                is_changed = true;
141                merge_into_value_map(dst, &value.value, self.case_on)
142            }
143            _ => self.value,
144        };
145
146        if is_changed {
147            self.unseal();
148        }
149        self
150    }
151
152    /// Return deserialized data of any type which implements [`Deserialize`] trait for given key path represented
153    /// as iterator. If given key path does not exists `Ok(None)` will be returned.
154    ///
155    /// # Errors
156    ///
157    /// If any errors will occur then error will be returned.
158    ///
159    /// # Example
160    ///
161    /// ```
162    /// use irx_config::{json, Value};
163    ///
164    /// let logger = Value::try_from(json!({
165    ///     "logger": {
166    ///         "id": 42,
167    ///         "host": "localhost"
168    ///     }
169    /// }))?;
170    ///
171    /// let id: u32 = logger.get_by_keys(["logger", "id"])?.unwrap();
172    /// ```
173    pub fn get_by_keys<I, K, T>(&self, keys: I) -> Result<Option<T>>
174    where
175        I: IntoIterator<Item = K>,
176        K: AsRef<str>,
177        T: DeserializeOwned,
178    {
179        let mut result = &self.value;
180        for key in keys {
181            result = if let InnerValue::Object(map) = result {
182                let key = crate::normalize_case(key.as_ref(), self.case_on);
183                match map.get(key.as_ref()) {
184                    None => return Ok(None),
185                    Some(v) => v,
186                }
187            } else {
188                return Ok(None);
189            };
190        }
191
192        Ok(Some(get(result.clone())?))
193    }
194
195    /// Return deserialized data of any type which implements [`Deserialize`] trait for given key path represented
196    /// as string with default keys level delimiter [`DEFAULT_KEYS_SEPARATOR`]. If given key path does not exists
197    /// `Ok(None)` will be returned.
198    ///
199    /// # Errors
200    ///
201    /// If any errors will occur then error will be returned.
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use irx_config::{json, Value};
207    ///
208    /// let logger = Value::try_from(json!({
209    ///     "logger": {
210    ///         "id": 42,
211    ///         "host": "localhost"
212    ///     }
213    /// }))?;
214    ///
215    /// let id: u32 = logger.get_by_key_path("logger:id")?.unwrap();
216    /// ```
217    #[inline]
218    pub fn get_by_key_path<T, P>(&self, path: P) -> Result<Option<T>>
219    where
220        T: DeserializeOwned,
221        P: AsRef<str>,
222    {
223        self.get_by_key_path_with_delim(path.as_ref(), DEFAULT_KEYS_SEPARATOR)
224    }
225
226    /// Return deserialized data of any type which implements [`Deserialize`] trait for given key path represented
227    /// as string with given keys level delimiter. If given key path does not exists `Ok(None)` will be returned.
228    ///
229    /// # Errors
230    ///
231    /// If any errors will occur then error will be returned.
232    ///
233    /// # Example
234    ///
235    /// ```
236    /// use irx_config::{json, Value};
237    ///
238    /// let logger = Value::try_from(json!({
239    ///     "logger": {
240    ///         "id": 42,
241    ///         "host": "localhost"
242    ///     }
243    /// }))?;
244    ///
245    /// let host: String = logger.get_by_key_path_with_delim("logger/host", "/")?.unwrap();
246    /// ```
247    pub fn get_by_key_path_with_delim<T, P, D>(&self, path: P, delim: D) -> Result<Option<T>>
248    where
249        T: DeserializeOwned,
250        P: AsRef<str>,
251        D: AsRef<str>,
252    {
253        fn inner<T>(value: &Value, path: &str, delim: &str) -> Result<Option<T>>
254        where
255            T: DeserializeOwned,
256        {
257            if delim.is_empty() {
258                return Err(Error::EmptySeparator("get", path.into()));
259            }
260
261            if path.is_empty() {
262                return value.get_by_keys([""; 0]);
263            }
264
265            value.get_by_keys(path.split(delim))
266        }
267
268        inner(self, path.as_ref(), delim.as_ref())
269    }
270
271    /// Return deserialized data of any type which implements [`Deserialize`] trait.
272    ///
273    /// # Errors
274    ///
275    /// If any errors will occur then error will be returned.
276    ///
277    /// # Example
278    ///
279    /// ```
280    /// use irx_config::{json, Value};
281    /// use serde::Deserialize;
282    ///
283    /// #[derive(Deserialize)]
284    /// struct Logger {
285    ///     pub id: u32,
286    ///     pub host: String,
287    /// }
288    ///
289    /// #[derive(Deserialize)]
290    /// struct Config {
291    ///     logger: Logger,
292    /// }
293    ///
294    /// let config = Value::try_from(json!({
295    ///     "logger": {
296    ///         "id": 42,
297    ///         "host": "localhost"
298    ///     }
299    /// }))?;
300    ///
301    /// let config: Config = config.get()?;
302    /// ```
303    #[inline]
304    pub fn get<T: DeserializeOwned>(&self) -> Result<T> {
305        get(self.value.clone())
306    }
307
308    /// Set value of any type which implements [`Serialize`] trait for given key path represented as iterator.
309    /// If [`Value`] was sealed and set operation was successful then it will be in [`SealedState::Mutated`]. Return
310    /// previous value for same key path if any.
311    ///
312    /// # Errors
313    ///
314    /// If any errors will occur then error will be returned.
315    ///
316    /// # Example
317    ///
318    /// ```
319    /// use irx_config::Value;
320    ///
321    /// let mut value = Value::default();
322    /// value.set_by_keys(["logger", "id"], 42)?;
323    /// ```
324    pub fn set_by_keys<I, K, T>(&mut self, keys: I, value: T) -> Result<Option<Self>>
325    where
326        I: IntoIterator<Item = K>,
327        K: AsRef<str>,
328        T: Serialize,
329    {
330        let inner = || {
331            let mut result = &mut self.value;
332            let mut keys = keys.into_iter().peekable();
333            while let Some(key) = keys.next() {
334                let key = crate::normalize_case(key.as_ref(), self.case_on);
335                result = match result {
336                    InnerValue::Object(m) if keys.peek().is_none() => {
337                        return Ok(m
338                            .insert(key.into_owned(), set(value, self.case_on)?)
339                            .map(|r| Self {
340                                value: r,
341                                case_on: self.case_on,
342                                ..Default::default()
343                            }))
344                    }
345                    InnerValue::Object(m) => m.entry(key).or_insert_with(|| json!({})),
346                    _ => return Err(Error::NotMap),
347                }
348            }
349
350            let prev = self.clone();
351            self.value = set(value, self.case_on)?;
352            Ok(Some(prev))
353        };
354
355        let result = inner()?;
356        self.unseal();
357        Ok(result)
358    }
359
360    /// Set value of any type which implements [`Serialize`] trait for given key path represented as string with default
361    /// keys level delimiter [`DEFAULT_KEYS_SEPARATOR`]. If [`Value`] was sealed and set operation was successful then
362    /// it will be in [`SealedState::Mutated`]. Return previous value for same key path if any.
363    ///
364    /// # Errors
365    ///
366    /// If any errors will occur then error will be returned.
367    ///
368    /// # Example
369    ///
370    /// ```
371    /// use irx_config::Value;
372    ///
373    /// let mut value = Value::default();
374    /// value.set_by_key_path("logger:id", 42)?;
375    /// ```
376    #[inline]
377    pub fn set_by_key_path<T, P>(&mut self, path: P, value: T) -> Result<Option<Self>>
378    where
379        T: Serialize,
380        P: AsRef<str>,
381    {
382        self.set_by_key_path_with_delim(path.as_ref(), DEFAULT_KEYS_SEPARATOR, value)
383    }
384
385    /// Set value of any type which implements [`Serialize`] trait for given key path represented as string with given
386    /// keys level delimiter. If [`Value`] was sealed and set operation was successful then it will be in
387    /// [`SealedState::Mutated`]. Return previous value for same key path if any.
388    ///
389    /// # Errors
390    ///
391    /// If any errors will occur then error will be returned.
392    ///
393    /// # Example
394    ///
395    /// ```
396    /// use irx_config::Value;
397    ///
398    /// let mut value = Value::default();
399    /// value.set_by_key_path_with_delim("logger/id", "/", 42)?;
400    /// ```
401    pub fn set_by_key_path_with_delim<T, P, D>(
402        &mut self,
403        path: P,
404        delim: D,
405        value: T,
406    ) -> Result<Option<Self>>
407    where
408        T: Serialize,
409        P: AsRef<str>,
410        D: AsRef<str>,
411    {
412        fn inner<T>(this: &mut Value, path: &str, delim: &str, value: T) -> Result<Option<Value>>
413        where
414            T: Serialize,
415        {
416            if delim.is_empty() {
417                return Err(Error::EmptySeparator("set", path.into()));
418            }
419
420            if path.is_empty() {
421                return this.set_by_keys([""; 0], value);
422            }
423
424            this.set_by_keys(path.split(delim), value)
425        }
426
427        inner(self, path.as_ref(), delim.as_ref(), value)
428    }
429
430    /// Return [`Value`] structure as a sequence of bytes.
431    #[inline]
432    pub fn as_bytes(&self) -> Vec<u8> {
433        self.value.to_string().as_bytes().to_owned()
434    }
435
436    /// Seal secret values in [`Value`] structure with given suffix. Such values will be obfuscated with `********`
437    /// during display/debugging output. If not set then all values will be displayed as is.
438    ///
439    /// # Example
440    ///
441    /// ```
442    /// use irx_config::Value;
443    ///
444    /// let mut value = Value::try_from(json!({
445    ///     "user": "user name",
446    ///     "password_sealed_": "secret"
447    /// }))?;
448    ///
449    /// value.seal("_sealed_");
450    /// ```
451    pub fn seal<S>(&mut self, suffix: S) -> &mut Self
452    where
453        S: AsRef<str>,
454    {
455        fn inner<'a>(this: &'a mut Value, suffix: &str) -> &'a mut Value {
456            if SealedState::On == this.sealed_state {
457                return this;
458            }
459
460            this.sealed = None;
461            this.sealed_state = SealedState::On;
462
463            if suffix.is_empty() {
464                return this;
465            }
466
467            if let InnerValue::Object(ref map) = this.value {
468                let (v, s) = get_sealed(map, suffix, this.case_on);
469                this.value = v;
470                this.sealed = s;
471            }
472            this
473        }
474
475        inner(self, suffix.as_ref())
476    }
477
478    /// Return `true` if [`Value`] is sealed, otherwise return `false`.
479    #[inline]
480    pub fn is_sealed(&self) -> bool {
481        SealedState::On == self.sealed_state
482    }
483
484    /// Return sealed state [`SealedState`].
485    #[inline]
486    pub fn sealed_state(&self) -> SealedState {
487        self.sealed_state
488    }
489
490    fn normalize_case(&mut self, case_on: bool) -> bool {
491        if case_on == self.case_on {
492            return false;
493        }
494
495        if let InnerValue::Object(ref map) = self.value {
496            self.case_on = case_on;
497            if case_on {
498                return false;
499            }
500            self.value = unicase_value_map(map);
501            return true;
502        }
503
504        false
505    }
506
507    fn get_sealed(&self) -> CowInnerValue {
508        if SealedState::Mutated == self.sealed_state {
509            return CowInnerValue::Owned(json!({}));
510        }
511
512        if let Some(ref s) = self.sealed {
513            if let InnerValue::Object(ref m) = self.value {
514                return CowInnerValue::Owned(merge_into_value_map(m.clone(), s, self.case_on));
515            }
516        }
517        CowInnerValue::Borrowed(&self.value)
518    }
519
520    fn unseal(&mut self) {
521        if SealedState::On == self.sealed_state {
522            self.sealed_state = SealedState::Mutated;
523        }
524        self.sealed = None;
525    }
526}
527
528impl Default for Value {
529    #[inline]
530    fn default() -> Self {
531        Self {
532            value: json!({}),
533            sealed: None,
534            sealed_state: SealedState::None,
535            case_on: true,
536        }
537    }
538}
539
540impl PartialEq for Value {
541    #[inline]
542    fn eq(&self, other: &Self) -> bool {
543        self.value == other.value
544    }
545}
546
547impl Eq for Value {}
548
549impl Serialize for Value {
550    #[inline]
551    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
552    where
553        S: Serializer,
554    {
555        self.value.serialize(serializer)
556    }
557}
558
559impl<'de> Deserialize<'de> for Value {
560    #[inline]
561    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
562    where
563        D: Deserializer<'de>,
564    {
565        Ok(Self {
566            value: InnerValue::deserialize(deserializer)?,
567            ..Default::default()
568        })
569    }
570}
571
572impl Debug for Value {
573    #[inline]
574    fn fmt(&self, f: &mut Formatter) -> FmtResult {
575        f.write_fmt(format_args!(
576            "Value {{ value: {:?}, sealed: {:?}, sealed_state: {:?}, case_on: {:?} }}",
577            self.get_sealed(),
578            self.sealed,
579            self.sealed_state,
580            self.case_on
581        ))
582    }
583}
584
585impl Display for Value {
586    #[inline]
587    fn fmt(&self, f: &mut Formatter) -> FmtResult {
588        let value = serde_json::to_string_pretty(&self.get_sealed()).map_err(|_| FmtError)?;
589        f.write_str(&value)
590    }
591}
592
593fn merge_into_value_map(dst: ValueMap, src: &InnerValue, case_on: bool) -> InnerValue {
594    if let InnerValue::Object(src) = src {
595        let mut result = dst;
596        for (k, v) in src {
597            let norm_key = crate::normalize_case(k, case_on);
598            let val = if let Some(InnerValue::Object(m)) = result.get(norm_key.as_ref()) {
599                merge_into_value_map(m.clone(), v, case_on)
600            } else {
601                v.clone()
602            };
603
604            result.insert(norm_key.into_owned(), val);
605        }
606        return InnerValue::Object(result);
607    }
608    src.clone()
609}
610
611fn unicase_value_map(map: &ValueMap) -> InnerValue {
612    let mut result = ValueMap::default();
613    for (k, v) in map {
614        let val = if let InnerValue::Object(m) = v {
615            unicase_value_map(m)
616        } else {
617            v.clone()
618        };
619        result.insert(crate::unicase(k), val);
620    }
621    InnerValue::Object(result)
622}
623
624#[inline]
625fn get<T: DeserializeOwned>(value: InnerValue) -> Result<T> {
626    serde_json::from_value(value)
627        .map_err(|e| Error::SerdeError(e, "Failed to deserialize value".into()))
628}
629
630fn set<T: Serialize>(value: T, case_on: bool) -> Result<InnerValue> {
631    let value = serde_json::to_value(value)
632        .map_err(|e| Error::SerdeError(e, "Failed to serialize value".into()))?;
633    Ok(match value {
634        InnerValue::Object(ref map) if !case_on => unicase_value_map(map),
635        _ => value,
636    })
637}
638
639fn get_sealed(value: &ValueMap, suffix: &str, case_on: bool) -> (InnerValue, Option<InnerValue>) {
640    let mut result = ValueMap::default();
641    let mut sealed = ValueMap::default();
642    let uni_suffix = crate::normalize_case(suffix, case_on);
643    for (k, v) in value {
644        let key = crate::normalize_case(k, case_on);
645        let (val, opt) = if let Some(InnerValue::Object(nested)) = value.get(key.as_ref()) {
646            let (v, s) = get_sealed(nested, suffix, case_on);
647            (CowInnerValue::Owned(v), s)
648        } else {
649            (CowInnerValue::Borrowed(v), None)
650        };
651
652        let norm_key = key.trim_end_matches(uni_suffix.as_ref());
653        result.insert(norm_key.to_string(), val.into_owned());
654        if key.len() != norm_key.len() {
655            sealed.insert(norm_key.to_string(), json!("********"));
656        } else if let Some(s) = opt {
657            sealed.insert(norm_key.to_string(), s);
658        }
659    }
660    (
661        InnerValue::Object(result),
662        if sealed.is_empty() {
663            None
664        } else {
665            Some(InnerValue::Object(sealed))
666        },
667    )
668}