1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! A general builder struct to allow for adding sources gradually, instead of requiring
//! producing your own list for [`build_from_sources`].
//!
//! The sources are consumed in the order they are provided, with priority given to the first
//! source. E.g., If sources `Source::File("config.toml")` and `Source::File("defaults.toml")` are
//! provided any values specified in `config.toml` take precedence over `defaults.toml`.
//!
//! A builder will generally be created by calling [`ConfigBuilder::default`], sources will be added
//! with [`ConfigBuilder::override_with`] which overrides existing source with the new source, and
//! then your configuration built with [`ConfigBuilder::try_build`].

use std::{marker::PhantomData, mem};

use confik::sources::DefaultSource;

use crate::{
    build_from_sources,
    sources::{DynSource, Source},
    Configuration, Error,
};

/// Used to accumulate ordered sources from which its `Target` is to be built.
///
/// An instance of this can be created via [`Configuration::builder`] or
/// [`ConfigBuilder::<T>::default`].
///
/// # Examples
///
/// Using [`Configuration::builder`]:
///
/// ```
/// # #[cfg(feature = "toml")]
/// # {
/// use confik::{Configuration, TomlSource};
///
/// #[derive(Debug, PartialEq, Configuration)]
/// struct MyConfigType {
///     param: String,
/// }
///
/// let config = MyConfigType::builder()
///     .override_with(TomlSource::new(r#"param = "Hello World""#))
///     .try_build()
///     .expect("Failed to build");
///
/// assert_eq!(config.param, "Hello World");
/// # }
/// ```
///
/// Using [`ConfigBuilder::<T>::default`]:
///
/// ```
/// # #[cfg(feature = "toml")]
/// # {
/// use confik::{ConfigBuilder, Configuration, TomlSource};
///
/// #[derive(Debug, PartialEq, Configuration)]
/// struct MyConfigType {
///     param: String,
/// }
///
/// let config = ConfigBuilder::<MyConfigType>::default()
///     .override_with(TomlSource::new(r#"param = "Hello World""#))
///     .try_build()
///     .expect("Failed to build");
///
/// assert_eq!(config.param, "Hello World");
/// # }
/// ```
pub struct ConfigBuilder<'a, Target: Configuration> {
    sources: Vec<Box<dyn DynSource<Target::Builder> + 'a>>,

    /// Use the generic parameter
    _phantom: PhantomData<fn() -> Target>,
}

impl<'a, Target: Configuration> ConfigBuilder<'a, Target> {
    /// Add a single [`Source`] to the list of sources.
    ///
    /// The source is added at the end of the list, overriding existing sources.
    ///
    /// ```
    /// # #[cfg(feature = "toml")]
    /// # {
    /// use confik::{Configuration, TomlSource};
    /// #[derive(Debug, PartialEq, Configuration)]
    /// struct MyConfigType {
    ///     param: String,
    /// }
    ///
    /// let config = MyConfigType::builder()
    ///     .override_with(TomlSource::new(r#"param = "Hello World""#))
    ///     .override_with(TomlSource::new(r#"param = "Hello Universe""#))
    ///     .try_build()
    ///     .expect("Failed to build");
    ///
    /// assert_eq!(config.param, "Hello Universe");
    /// # }
    /// ```
    pub fn override_with(&mut self, source: impl Source + 'a) -> &mut Self {
        self.sources.push(Box::new(source));
        self
    }

    /// Attempt to build from the provided sources.
    pub fn try_build(&mut self) -> Result<Target, Error> {
        if self.sources.is_empty() {
            build_from_sources([Box::new(DefaultSource) as Box<dyn DynSource<_>>])
        } else {
            build_from_sources(mem::take(&mut self.sources).into_iter().rev())
        }
    }
}

impl<'a, Target: Configuration> Default for ConfigBuilder<'a, Target> {
    fn default() -> Self {
        Self {
            sources: Vec::new(),
            _phantom: PhantomData,
        }
    }
}