smart_config/
value.rs

1//! Enriched JSON object model that allows to associate values with origins.
2
3use std::{collections::BTreeMap, fmt, iter, mem, sync::Arc};
4
5pub use secrecy::{ExposeSecret, SecretString};
6
7use crate::metadata::BasicTypes;
8
9/// Supported file formats.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[non_exhaustive]
12pub enum FileFormat {
13    /// JSON file.
14    Json,
15    /// YAML file.
16    Yaml,
17    /// `.env` file.
18    Dotenv,
19}
20
21impl fmt::Display for FileFormat {
22    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
23        formatter.write_str(match self {
24            Self::Json => "JSON",
25            Self::Yaml => "YAML",
26            Self::Dotenv => ".env",
27        })
28    }
29}
30
31/// Origin of a [`Value`] in configuration input.
32#[derive(Debug, Default)]
33#[non_exhaustive]
34pub enum ValueOrigin {
35    /// Unknown / default origin.
36    #[default]
37    Unknown,
38    /// Environment variables.
39    EnvVars,
40    /// Fallbacks for config params.
41    Fallbacks,
42    /// File source.
43    File {
44        /// Filename; may not correspond to a real filesystem path.
45        name: String,
46        /// File format.
47        format: FileFormat,
48    },
49    /// Path from a structured source.
50    Path {
51        /// Source of structured data, e.g. a JSON file.
52        source: Arc<Self>,
53        /// Dot-separated path in the source, like `api.http.port`.
54        path: String,
55    },
56    /// Synthetic value.
57    Synthetic {
58        /// Original value source.
59        source: Arc<Self>,
60        /// Human-readable description of the transform.
61        transform: String,
62    },
63}
64
65impl fmt::Display for ValueOrigin {
66    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Unknown => formatter.write_str("unknown"),
69            Self::EnvVars => formatter.write_str("env variables"),
70            Self::Fallbacks => formatter.write_str("fallbacks"),
71            Self::File { name, format } => {
72                write!(formatter, "{format} file '{name}'")
73            }
74            Self::Path { source, path } => {
75                if matches!(source.as_ref(), ValueOrigin::EnvVars) {
76                    write!(formatter, "env variable '{path}'")
77                } else {
78                    write!(formatter, "{source} -> path '{path}'")
79                }
80            }
81            Self::Synthetic { source, transform } => {
82                write!(formatter, "{source} -> {transform}")
83            }
84        }
85    }
86}
87
88/// String value: either a plaintext one, or a secret.
89#[derive(Clone)]
90pub enum StrValue {
91    /// Plain string value.
92    Plain(String),
93    /// Secret string value.
94    Secret(SecretString),
95}
96
97impl StrValue {
98    /// Exposes a secret string if appropriate.
99    pub fn expose(&self) -> &str {
100        match self {
101            Self::Plain(s) => s,
102            Self::Secret(s) => s.expose_secret(),
103        }
104    }
105
106    pub(crate) fn is_secret(&self) -> bool {
107        matches!(self, Self::Secret(_))
108    }
109
110    pub(crate) fn make_secret(&mut self) {
111        match self {
112            Self::Plain(s) => {
113                *self = Self::Secret(mem::take(s).into());
114            }
115            Self::Secret(_) => { /* value is already secret; do nothing */ }
116        }
117    }
118}
119
120impl fmt::Debug for StrValue {
121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Plain(s) => fmt::Debug::fmt(s, formatter),
124            Self::Secret(_) => formatter.write_str("[REDACTED]"),
125        }
126    }
127}
128
129impl fmt::Display for StrValue {
130    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
131        formatter.write_str(match self {
132            Self::Plain(s) => s,
133            Self::Secret(_) => "[REDACTED]",
134        })
135    }
136}
137
138/// JSON value with additional origin information.
139#[derive(Clone, Default)]
140pub enum Value {
141    /// `null`.
142    #[default]
143    Null,
144    /// Boolean value.
145    Bool(bool),
146    /// Numeric value.
147    Number(serde_json::Number),
148    /// String value.
149    String(StrValue),
150    /// Array of values.
151    Array(Vec<WithOrigin>),
152    /// Object / map of values.
153    Object(Map),
154}
155
156impl fmt::Debug for Value {
157    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            Self::Null => formatter.write_str("null"),
160            Self::Bool(value) => fmt::Display::fmt(value, formatter),
161            Self::Number(value) => fmt::Display::fmt(value, formatter),
162            Self::String(value) => fmt::Debug::fmt(value, formatter),
163            Self::Array(array) => formatter.debug_list().entries(array).finish(),
164            Self::Object(map) => formatter.debug_map().entries(map).finish(),
165        }
166    }
167}
168
169impl From<bool> for Value {
170    fn from(value: bool) -> Self {
171        Self::Bool(value)
172    }
173}
174
175impl PartialEq<bool> for Value {
176    fn eq(&self, other: &bool) -> bool {
177        match self {
178            Self::Bool(val) => val == other,
179            _ => false,
180        }
181    }
182}
183
184impl From<serde_json::Number> for Value {
185    fn from(value: serde_json::Number) -> Self {
186        Self::Number(value)
187    }
188}
189
190impl PartialEq<serde_json::Number> for Value {
191    fn eq(&self, other: &serde_json::Number) -> bool {
192        match self {
193            Self::Number(num) => num == other,
194            _ => false,
195        }
196    }
197}
198
199macro_rules! impl_traits_for_number {
200    ($num:ty) => {
201        impl From<$num> for Value {
202            fn from(value: $num) -> Self {
203                Self::Number(value.into())
204            }
205        }
206
207        impl PartialEq<$num> for Value {
208            fn eq(&self, other: &$num) -> bool {
209                match self {
210                    Self::Number(num) => *num == (*other).into(),
211                    _ => false,
212                }
213            }
214        }
215    };
216}
217
218impl_traits_for_number!(u64);
219impl_traits_for_number!(i64);
220
221impl From<String> for Value {
222    fn from(value: String) -> Self {
223        Self::String(StrValue::Plain(value))
224    }
225}
226
227impl From<Vec<WithOrigin>> for Value {
228    fn from(array: Vec<WithOrigin>) -> Self {
229        Self::Array(array)
230    }
231}
232
233impl From<Map> for Value {
234    fn from(map: Map) -> Self {
235        Self::Object(map)
236    }
237}
238
239impl Value {
240    pub(crate) fn is_supported_by(&self, types: BasicTypes) -> bool {
241        match self {
242            Self::Null => true,
243            Self::Bool(_) => types.contains(BasicTypes::BOOL),
244            Self::Number(number) if number.is_u64() || number.is_i64() => {
245                types.contains(BasicTypes::INTEGER)
246            }
247            Self::Number(_) => types.contains(BasicTypes::FLOAT),
248            Self::String(_) => {
249                // Relax type consistency check in order to be able to deserialize numbers / bools
250                // (which is supported on the `ValueDeserializer` level).
251                types.contains(BasicTypes::STRING)
252                    || types.contains(BasicTypes::INTEGER)
253                    || types.contains(BasicTypes::BOOL)
254            }
255            Self::Array(_) => types.contains(BasicTypes::ARRAY),
256            Self::Object(_) => types.contains(BasicTypes::OBJECT),
257        }
258    }
259
260    /// Attempts to convert this value to a plain (non-secret) string.
261    pub fn as_plain_str(&self) -> Option<&str> {
262        match self {
263            Self::String(StrValue::Plain(s)) => Some(s),
264            _ => None,
265        }
266    }
267
268    /// Attempts to convert this value to an object.
269    pub fn as_object(&self) -> Option<&Map> {
270        match self {
271            Self::Object(map) => Some(map),
272            _ => None,
273        }
274    }
275}
276
277/// JSON object.
278pub type Map<V = Value> = BTreeMap<String, WithOrigin<V>>;
279
280/// JSON value together with its origin.
281#[derive(Debug, Clone, Default)]
282pub struct WithOrigin<T = Value> {
283    /// Inner value.
284    pub inner: T,
285    /// Origin of the value.
286    pub origin: Arc<ValueOrigin>,
287}
288
289impl<T> WithOrigin<T> {
290    /// Creates a new value with origin.
291    pub fn new(inner: T, origin: Arc<ValueOrigin>) -> Self {
292        Self { inner, origin }
293    }
294
295    pub(crate) fn set_origin_if_unset(mut self, origin: &Arc<ValueOrigin>) -> Self {
296        if matches!(self.origin.as_ref(), ValueOrigin::Unknown) {
297            self.origin = origin.clone();
298        }
299        self
300    }
301
302    pub(crate) fn map<U>(self, map_fn: impl FnOnce(T) -> U) -> WithOrigin<U> {
303        WithOrigin {
304            inner: map_fn(self.inner),
305            origin: self.origin,
306        }
307    }
308}
309
310impl WithOrigin {
311    pub(crate) fn get(&self, pointer: Pointer<'_>) -> Option<&Self> {
312        pointer
313            .segments()
314            .try_fold(self, |ptr, segment| match &ptr.inner {
315                Value::Object(map) => map.get(segment),
316                Value::Array(array) => array.get(segment.parse::<usize>().ok()?),
317                _ => None,
318            })
319    }
320
321    /// Returns value at the specified pointer.
322    pub fn pointer(&self, pointer: &str) -> Option<&Self> {
323        self.get(Pointer(pointer))
324    }
325
326    pub(crate) fn get_mut(&mut self, pointer: Pointer) -> Option<&mut Self> {
327        pointer
328            .segments()
329            .try_fold(self, |ptr, segment| match &mut ptr.inner {
330                Value::Object(map) => map.get_mut(segment),
331                Value::Array(array) => array.get_mut(segment.parse::<usize>().ok()?),
332                _ => None,
333            })
334    }
335
336    /// Ensures that there is an object (possibly empty) at the specified location.
337    pub(crate) fn ensure_object(
338        &mut self,
339        at: Pointer<'_>,
340        mut create_origin: impl FnMut(Pointer<'_>) -> Arc<ValueOrigin>,
341    ) -> &mut Map {
342        for ancestor_path in at.with_ancestors() {
343            self.ensure_object_step(ancestor_path, &mut create_origin);
344        }
345
346        let Value::Object(map) = &mut self.get_mut(at).unwrap().inner else {
347            unreachable!(); // Ensured by calls above
348        };
349        map
350    }
351
352    fn ensure_object_step(
353        &mut self,
354        at: Pointer<'_>,
355        mut create_origin: impl FnMut(Pointer<'_>) -> Arc<ValueOrigin>,
356    ) {
357        let Some((parent, last_segment)) = at.split_last() else {
358            // Nothing to do.
359            return;
360        };
361
362        // `unwrap()` is safe since `ensure_object()` is always called for the parent
363        let parent = &mut self.get_mut(parent).unwrap().inner;
364        if !matches!(parent, Value::Object(_)) {
365            *parent = Value::Object(Map::new());
366        }
367        let Value::Object(parent_object) = parent else {
368            unreachable!();
369        };
370
371        if !parent_object.contains_key(last_segment) {
372            parent_object.insert(
373                last_segment.to_owned(),
374                WithOrigin {
375                    inner: Value::Object(Map::new()),
376                    origin: create_origin(at),
377                },
378            );
379        }
380    }
381
382    /// Deep-merges self and `other`, with `other` having higher priority. Only objects are meaningfully merged;
383    /// all other values are replaced.
384    pub(crate) fn deep_merge(&mut self, overrides: Self) {
385        match (&mut self.inner, overrides.inner) {
386            (Value::Object(this), Value::Object(other)) => {
387                Self::deep_merge_into_map(this, other);
388            }
389            (this, value) => {
390                *this = value;
391                self.origin = overrides.origin;
392            }
393        }
394    }
395
396    fn deep_merge_into_map(dest: &mut Map, source: Map) {
397        for (key, value) in source {
398            if let Some(existing_value) = dest.get_mut(&key) {
399                existing_value.deep_merge(value);
400            } else {
401                dest.insert(key, value);
402            }
403        }
404    }
405}
406
407// TODO: make public for increased type safety?
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
409pub(crate) struct Pointer<'a>(pub &'a str);
410
411impl fmt::Display for Pointer<'_> {
412    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
413        formatter.write_str(self.0)
414    }
415}
416
417impl<'a> Pointer<'a> {
418    pub(crate) fn segments(self) -> impl Iterator<Item = &'a str> {
419        self.0
420            .split('.')
421            .take(if self.0.is_empty() { 0 } else { usize::MAX })
422    }
423
424    pub(crate) fn split_last(self) -> Option<(Self, &'a str)> {
425        if self.0.is_empty() {
426            None
427        } else if let Some((parent, last_segment)) = self.0.rsplit_once('.') {
428            Some((Self(parent), last_segment))
429        } else {
430            Some((Self(""), self.0))
431        }
432    }
433
434    /// Includes `Self`; doesn't include the empty pointer.
435    pub(crate) fn with_ancestors(self) -> impl Iterator<Item = Self> {
436        let mut current = self.0;
437        iter::from_fn(move || {
438            if current.is_empty() {
439                None
440            } else if let Some((_, tail)) = current.split_once('.') {
441                current = tail;
442                Some(Self(&self.0[..self.0.len() - tail.len() - 1]))
443            } else {
444                current = "";
445                Some(self)
446            }
447        })
448    }
449
450    pub(crate) fn join(self, suffix: &str) -> String {
451        if suffix.is_empty() {
452            self.0.to_owned()
453        } else if self.0.is_empty() {
454            suffix.to_owned()
455        } else {
456            format!("{}.{suffix}", self.0)
457        }
458    }
459
460    /// Returns `None` if `self` doesn't contain a sufficient number of segments.
461    pub(crate) fn join_path(mut self, suffix: Pointer<'_>) -> Option<String> {
462        let prefix_dots = suffix.0.bytes().take_while(|&ch| ch == b'.').count();
463        for _ in 0..prefix_dots.saturating_sub(1) {
464            (self, _) = self.split_last()?;
465        }
466        Some(self.join(&suffix.0[prefix_dots..]))
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473
474    #[test]
475    fn splitting_pointer() {
476        let pointer = Pointer("");
477        assert_eq!(pointer.split_last(), None);
478        assert_eq!(pointer.segments().collect::<Vec<_>>(), [] as [&str; 0]);
479        assert_eq!(pointer.with_ancestors().collect::<Vec<_>>(), []);
480
481        let pointer = Pointer("test");
482        assert_eq!(pointer.split_last(), Some((Pointer(""), "test")));
483        assert_eq!(pointer.segments().collect::<Vec<_>>(), ["test"]);
484        assert_eq!(
485            pointer.with_ancestors().collect::<Vec<_>>(),
486            [Pointer("test")]
487        );
488
489        let pointer = Pointer("test.value");
490        assert_eq!(pointer.split_last(), Some((Pointer("test"), "value")));
491        assert_eq!(pointer.segments().collect::<Vec<_>>(), ["test", "value"]);
492        assert_eq!(
493            pointer.with_ancestors().collect::<Vec<_>>(),
494            [Pointer("test"), Pointer("test.value")]
495        );
496    }
497
498    #[test]
499    fn joining_pointers() {
500        let pointer = Pointer("");
501        let joined = pointer.join("test");
502        assert_eq!(joined, "test");
503
504        let pointer = Pointer("test");
505        let joined = pointer.join("");
506        assert_eq!(joined, "test");
507
508        let pointer = Pointer("test");
509        let joined = pointer.join("other");
510        assert_eq!(joined, "test.other");
511    }
512
513    #[test]
514    fn joining_pointer_paths() {
515        let pointer = Pointer("");
516        let joined = pointer.join_path(Pointer("test")).unwrap();
517        assert_eq!(joined, "test");
518
519        let pointer = Pointer("");
520        let joined = pointer.join_path(Pointer(".test")).unwrap();
521        assert_eq!(joined, "test");
522
523        let pointer = Pointer("");
524        let joined = pointer.join_path(Pointer(".test.value")).unwrap();
525        assert_eq!(joined, "test.value");
526
527        let pointer = Pointer("map");
528        let joined = pointer.join_path(Pointer(".test.value")).unwrap();
529        assert_eq!(joined, "map.test.value");
530
531        let pointer = Pointer("map");
532        let joined = pointer.join_path(Pointer("..test.value")).unwrap();
533        assert_eq!(joined, "test.value");
534
535        let pointer = Pointer("map.key");
536        let joined = pointer.join_path(Pointer("..test.value")).unwrap();
537        assert_eq!(joined, "map.test.value");
538
539        let pointer = Pointer("map.key");
540        let joined = pointer.join_path(Pointer("...test.value")).unwrap();
541        assert_eq!(joined, "test.value");
542    }
543}