manaconf/
lib.rs

1//! # manaconf
2//!
3//! Library for building a layered configuration provider.
4//!
5//! TODO: Better docs here
6
7use std::error::Error;
8
9mod helpers;
10mod key;
11mod value;
12pub mod sources;
13
14pub use key::{Key, KeyBuf};
15pub use value::{TryFromValue, Value};
16
17/// Trait implemented by config value sources
18pub trait Source {
19    /// The error type used by this `Source`
20    type Error;
21
22    /// Gets a value from the source with the given `key`
23    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error>;
24}
25
26/// Wraps a source to map it's error to `Box<dyn Error>`
27struct BoxedErrorSource<S> {
28    contained: S,
29}
30
31impl<S, E> From<S> for BoxedErrorSource<S>
32where
33    E: Error + 'static,
34    S: Source<Error = E>,
35{
36    fn from(source: S) -> Self {
37        BoxedErrorSource { contained: source }
38    }
39}
40
41impl<S, E> Source for BoxedErrorSource<S>
42where
43    E: Error + 'static,
44    S: Source<Error = E>,
45{
46    type Error = Box<dyn Error>;
47
48    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error> {
49        self.contained
50            .get_value(key)
51            .map_err(|e| Box::new(e) as Box<dyn Error>)
52    }
53}
54
55/// Error returned from an implementation of `ValueRead`
56#[derive(Debug)]
57pub enum ValueReadError {
58    /// Error occurred when reading from a `Source`
59    SourceReadError(Box<dyn Error>),
60    /// Error occurred when attempting to convert a `Value`
61    /// to a requested type
62    ValueConversionError(Box<dyn Error>),
63    /// Value was not present, but was expected to be so
64    ValueNotPresent,
65}
66
67impl Error for ValueReadError {}
68impl std::fmt::Display for ValueReadError {
69    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            ValueReadError::SourceReadError(e) => {
72                write!(
73                    fmt,
74                    "Error occurred while reading from configuration source: "
75                )?;
76                e.fmt(fmt)
77            }
78            ValueReadError::ValueConversionError(e) => {
79                write!(fmt, "Value failed to be converted to requested type: ")?;
80                e.fmt(fmt)
81            }
82            ValueReadError::ValueNotPresent => write!(fmt, "Requested value did not exist"),
83        }
84    }
85}
86
87pub trait ValueRead: Sized {
88    /// Gets a value from configuration using the given `key`
89    ///
90    /// Attempts to convert the value to `T` if the conversion is supported
91    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
92    where
93        K: AsRef<Key>,
94        T: TryFromValue;
95
96    /// Gets a value from configuration using the given `key` where the value
97    /// is expected to exist, and thus it's an error if it doesn't.
98    ///
99    /// Attempts to convert the value to `T` if the conversion is supported
100    fn get_expected_value<T, K>(&self, key: K) -> Result<T, ValueReadError>
101    where
102        K: AsRef<Key>,
103        T: TryFromValue,
104    {
105        self.get_value(key)?.ok_or(ValueReadError::ValueNotPresent)
106    }
107
108    /// Bind values to type `T`
109    fn bind<T: TryFromValueRead>(&self) -> Result<T, ValueReadError> {
110        <T as TryFromValueRead>::try_from(self)
111    }
112}
113/// Trait to implement on types that can be constructed from reading values
114/// from a `ValueRead`
115pub trait TryFromValueRead: Sized {
116    fn try_from<R: ValueRead>(value_read: &R) -> Result<Self, ValueReadError>;
117}
118
119/// A section of configuration
120///
121/// This acts as a pre-applied prefix to a config key when fetching values
122///
123/// # Example
124/// ```
125/// let section = config.section("My::Config::Section");
126/// // This is now the same as requesting `My::Config::Section::Value`
127/// let value: String = section.get_value("Value");
128/// ```
129pub struct Section<'a> {
130    key: KeyBuf,
131    config: &'a Config,
132}
133
134impl<'a> Section<'a> {
135    /// Creates a further subsection from this section, similar to a section
136    /// created by calling `Config::section`, except in this case the current
137    /// sections prefix is prepended to the `key` passed into `section`
138    ///
139    /// # Example
140    /// ```
141    /// let section = config.section("first");
142    /// let subsection = section.section("second");
143    /// // Will read value from "first::second::value"
144    /// let value: String = subsection.get_value("value");
145    /// ```
146    pub fn section<K: AsRef<Key>>(&self, key: K) -> Section {
147        self.section(key.as_ref())
148    }
149
150    fn _section(&self, key: &Key) -> Section {
151        Section { 
152            key: self.key.extend_with_suffix(key),
153            config: self.config
154        }
155    }
156}
157
158impl<'a> ValueRead for Section<'a> {
159    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
160    where
161        K: AsRef<Key>,
162        T: TryFromValue,
163    {
164        let mut new_key = self.key.to_key_buf();
165        new_key.push(key.as_ref());
166        self.config.get_value(&new_key)
167    }
168}
169
170pub struct Config {
171    sources: Vec<Box<dyn Source<Error = Box<dyn Error>>>>,
172}
173
174impl Config {
175    /// Get a section of a config at a given key path
176    ///
177    /// This allows config values to be accessed from Section,
178    /// without having to specify the prefix
179    pub fn section<K: AsRef<Key>>(&self, key: K) -> Section {
180        Section {
181            key: key.as_ref().to_owned(),
182            config: self,
183        }
184    }
185
186    fn _get_value(&self, key: &Key) -> Result<Option<Value>, ValueReadError> {
187        if self.sources.len() == 0 {
188            return Ok(None);
189        }
190
191        // Go through our sources, skipping any that return `Ok(None)`
192        // and taking the first one to return `Ok(Some(_))` or `Err()`
193        self.sources
194            .iter()
195            .map(|s| s.get_value(key))
196            .skip_while(|v| match v {
197                Ok(None) => true,
198                _ => false,
199            })
200            .next()
201            .unwrap_or(Ok(None))
202            .map_err(|e| ValueReadError::SourceReadError(e))
203    }
204}
205
206impl ValueRead for Config {
207    fn get_value<T, K>(&self, key: K) -> Result<Option<T>, ValueReadError>
208    where
209        K: AsRef<Key>,
210        T: TryFromValue,
211    {
212        self._get_value(key.as_ref())?
213            .map(|v| T::try_from_value(v))
214            .transpose()
215            .map_err(|e| ValueReadError::ValueConversionError(Box::new(e)))
216    }
217}
218
219type BoxedDynamicSource = Box<dyn Source<Error = Box<dyn Error>>>;
220
221pub struct Builder {
222    sources: Vec<BoxedDynamicSource>,
223}
224
225impl Builder {
226    /// Creates a new `Builder` for constructing a `Config`
227    pub fn new() -> Self {
228        Self {
229            sources: Vec::new(),
230        }
231    }
232
233    /// Adds a new `Source` such that the sources provided keys are available
234    /// from the root.
235    ///
236    /// Note: The order that sources are added, are the order that they are
237    /// checked for values, so you want to add your source with the highest
238    /// priority first.
239    pub fn add_source<S, E>(mut self, source: S) -> Self
240    where
241        E: Error + 'static,
242        S: Source<Error = E> + 'static,
243    {
244        self.sources.push(Box::new(BoxedErrorSource::from(source)));
245        self
246    }
247
248    /// Adds a new `Source` such that the sources provided keys are available
249    /// from the supplied `prefix`.
250    ///
251    /// Note: The order that sources are added, are the order that they are
252    /// checked for values, so you want to add your source with the highest
253    /// priority first.
254    pub fn add_source_at_prefix<S, E, K>(self, source: S, prefix: K) -> Self
255    where
256        E: Error + 'static,
257        S: Source<Error = E> + 'static,
258        K: AsRef<Key>
259    {
260        self.add_source(WithPrefixSource { 
261            prefix: prefix.as_ref().to_owned(), 
262            source 
263        })
264    }
265
266    /// Build the `Config` from the setup supplied by this builder
267    pub fn build(self) -> Config {
268        Config {
269            sources: self.sources,
270        }
271    }
272}
273
274/// Wraps a source so that it only responds to keys with a given prefix.
275///
276/// Useful for mounting sources that otherwise would not have the desired
277/// prefix
278pub struct WithPrefixSource<S: Source> {
279    prefix: KeyBuf,
280    source: S,
281}
282
283impl<S: Source> Source for WithPrefixSource<S> {
284    type Error = S::Error;
285
286    fn get_value(&self, key: &Key) -> Result<Option<Value>, Self::Error> {
287        if !key.start_with(key) {
288            return Ok(None);
289        }
290
291        self.source.get_value(&key.strip_prefix(&self.prefix))
292    }
293}
294