Skip to main content

tanzim_source/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod impls;
4mod parse;
5
6pub use parse::{ParseError, parse};
7
8#[cfg(feature = "serde")]
9mod serde;
10
11use std::fmt::{Debug, Display, Formatter};
12
13/// Error from building or parsing a [`Source`].
14#[derive(Debug, thiserror::Error)]
15pub enum Error {
16    /// Builder has no source identifier (missing or empty).
17    #[error("configuration source is required")]
18    MissingSource,
19    /// Invalid configuration source string.
20    #[error(transparent)]
21    Parse(#[from] ParseError),
22}
23
24/// Kind of value stored in [`OptionValue`].
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum OptionValueType {
27    Bool,
28    Integer,
29    Float,
30    String,
31    Map,
32    List,
33}
34
35impl Display for OptionValueType {
36    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37        f.write_str(match self {
38            Self::Bool => "boolean",
39            Self::Integer => "integer",
40            Self::Float => "float",
41            Self::String => "string",
42            Self::Map => "map",
43            Self::List => "list",
44        })
45    }
46}
47
48/// Dynamically typed loader option or nested option map.
49#[derive(Debug, Clone, PartialEq)]
50pub enum OptionValue {
51    Bool(bool),
52    Integer(i64),
53    Float(f64),
54    String(String),
55    List(Vec<OptionValue>),
56    Map(Options),
57}
58
59impl OptionValue {
60    pub fn new_map() -> Self {
61        Self::Map(Options::default())
62    }
63
64    pub fn new_list() -> Self {
65        Self::List(Vec::new())
66    }
67
68    pub fn new_string() -> Self {
69        Self::String(String::new())
70    }
71
72    pub fn is_bool(&self) -> bool {
73        matches!(self, Self::Bool(_))
74    }
75
76    pub fn as_bool(&self) -> Option<bool> {
77        match self {
78            Self::Bool(value) => Some(*value),
79            _ => None,
80        }
81    }
82
83    pub fn into_bool(self) -> Option<bool> {
84        match self {
85            Self::Bool(value) => Some(value),
86            _ => None,
87        }
88    }
89
90    pub fn bool_mut(&mut self) -> Option<&mut bool> {
91        match self {
92            Self::Bool(value) => Some(value),
93            _ => None,
94        }
95    }
96
97    pub fn is_integer(&self) -> bool {
98        matches!(self, Self::Integer(_))
99    }
100
101    pub fn as_integer(&self) -> Option<i64> {
102        match self {
103            Self::Integer(value) => Some(*value),
104            _ => None,
105        }
106    }
107
108    pub fn into_integer(self) -> Option<i64> {
109        match self {
110            Self::Integer(value) => Some(value),
111            _ => None,
112        }
113    }
114
115    pub fn integer_mut(&mut self) -> Option<&mut i64> {
116        match self {
117            Self::Integer(value) => Some(value),
118            _ => None,
119        }
120    }
121
122    pub fn is_float(&self) -> bool {
123        matches!(self, Self::Float(_))
124    }
125
126    pub fn as_float(&self) -> Option<f64> {
127        match self {
128            Self::Float(value) => Some(*value),
129            _ => None,
130        }
131    }
132
133    pub fn into_float(self) -> Option<f64> {
134        match self {
135            Self::Float(value) => Some(value),
136            _ => None,
137        }
138    }
139
140    pub fn float_mut(&mut self) -> Option<&mut f64> {
141        match self {
142            Self::Float(value) => Some(value),
143            _ => None,
144        }
145    }
146
147    pub fn is_string(&self) -> bool {
148        matches!(self, Self::String(_))
149    }
150
151    pub fn as_string(&self) -> Option<&String> {
152        match self {
153            Self::String(value) => Some(value),
154            _ => None,
155        }
156    }
157
158    pub fn into_string(self) -> Option<String> {
159        match self {
160            Self::String(value) => Some(value),
161            _ => None,
162        }
163    }
164
165    pub fn string_mut(&mut self) -> Option<&mut String> {
166        match self {
167            Self::String(value) => Some(value),
168            _ => None,
169        }
170    }
171
172    pub fn is_list(&self) -> bool {
173        matches!(self, Self::List(_))
174    }
175
176    pub fn as_list(&self) -> Option<&Vec<OptionValue>> {
177        match self {
178            Self::List(value) => Some(value),
179            _ => None,
180        }
181    }
182
183    pub fn into_list(self) -> Option<Vec<OptionValue>> {
184        match self {
185            Self::List(value) => Some(value),
186            _ => None,
187        }
188    }
189
190    pub fn list_mut(&mut self) -> Option<&mut Vec<OptionValue>> {
191        match self {
192            Self::List(value) => Some(value),
193            _ => None,
194        }
195    }
196
197    pub fn is_map(&self) -> bool {
198        matches!(self, Self::Map(_))
199    }
200
201    pub fn as_map(&self) -> Option<&Options> {
202        match self {
203            Self::Map(value) => Some(value),
204            _ => None,
205        }
206    }
207
208    pub fn into_map(self) -> Option<Options> {
209        match self {
210            Self::Map(value) => Some(value),
211            _ => None,
212        }
213    }
214
215    pub fn map_mut(&mut self) -> Option<&mut Options> {
216        match self {
217            Self::Map(value) => Some(value),
218            _ => None,
219        }
220    }
221
222    pub fn type_name(&self) -> OptionValueType {
223        match self {
224            Self::Bool(_) => OptionValueType::Bool,
225            Self::Integer(_) => OptionValueType::Integer,
226            Self::Float(_) => OptionValueType::Float,
227            Self::String(_) => OptionValueType::String,
228            Self::List(_) => OptionValueType::List,
229            Self::Map(_) => OptionValueType::Map,
230        }
231    }
232}
233
234impl Display for OptionValue {
235    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236        match self {
237            Self::Bool(value) => write!(f, "{value}"),
238            Self::Integer(value) => write!(f, "{value}"),
239            Self::Float(value) => write!(f, "{value}"),
240            Self::String(value) => write!(f, "{value:?}"),
241            Self::List(value) => {
242                write!(f, "[")?;
243                for (index, inner_value) in value.iter().enumerate() {
244                    if index > 0 {
245                        write!(f, ", ")?;
246                    }
247                    write!(f, "{inner_value}")?;
248                }
249                write!(f, "]")
250            }
251            Self::Map(value) => write!(f, "{value}"),
252        }
253    }
254}
255
256/// Ordered map of loader options.
257#[derive(Debug, Clone, PartialEq, Default)]
258pub struct Options {
259    entries: Vec<(String, OptionValue)>,
260}
261
262impl Options {
263    pub fn new() -> Self {
264        Self::default()
265    }
266
267    pub fn len(&self) -> usize {
268        self.entries.len()
269    }
270
271    pub fn is_empty(&self) -> bool {
272        self.entries.is_empty()
273    }
274
275    pub fn contains_key(&self, key: &str) -> bool {
276        self.entries.iter().any(|(entry_key, _)| entry_key == key)
277    }
278
279    pub fn get(&self, key: &str) -> Option<&OptionValue> {
280        self.entries
281            .iter()
282            .rfind(|(entry_key, _)| entry_key == key)
283            .map(|(_, value)| value)
284    }
285
286    pub fn get_mut(&mut self, key: &str) -> Option<&mut OptionValue> {
287        let index = self
288            .entries
289            .iter()
290            .rposition(|(entry_key, _)| entry_key == key)?;
291        Some(&mut self.entries[index].1)
292    }
293
294    pub fn insert<K: Into<String>, V: Into<OptionValue>>(
295        &mut self,
296        key: K,
297        value: V,
298    ) -> Option<OptionValue> {
299        let key = key.into();
300        let value = value.into();
301        let old = self.remove(&key);
302        self.entries.push((key, value));
303        old
304    }
305
306    pub fn remove(&mut self, key: &str) -> Option<OptionValue> {
307        let index = self
308            .entries
309            .iter()
310            .rposition(|(entry_key, _)| entry_key == key)?;
311        Some(self.entries.remove(index).1)
312    }
313
314    pub fn iter(&self) -> impl Iterator<Item = (&str, &OptionValue)> {
315        self.entries
316            .iter()
317            .map(|(key, value)| (key.as_str(), value))
318    }
319
320    pub fn keys(&self) -> impl Iterator<Item = &str> {
321        self.entries.iter().map(|(key, _)| key.as_str())
322    }
323
324    pub fn values(&self) -> impl Iterator<Item = &OptionValue> {
325        self.entries.iter().map(|(_, value)| value)
326    }
327
328    pub(crate) fn entries(&self) -> &[(String, OptionValue)] {
329        &self.entries
330    }
331}
332
333impl Display for Options {
334    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
335        write!(f, "{{")?;
336        for (index, (key, value)) in self.entries.iter().enumerate() {
337            if index > 0 {
338                write!(f, ", ")?;
339            }
340            write!(f, "{key:?}: {value}")?;
341        }
342        write!(f, "}}")
343    }
344}
345
346/// One configuration source declaration.
347#[derive(Debug, Clone, PartialEq)]
348pub struct Source {
349    pub(crate) source: String,
350    pub(crate) options: Options,
351    pub(crate) resource: String,
352    pub(crate) skip_errors: bool,
353    pub(crate) resource_colon: bool,
354}
355
356impl Source {
357    pub fn parse(input: &str) -> Result<Self, ParseError> {
358        parse::parse(input)
359    }
360
361    pub fn source(&self) -> &str {
362        self.source.as_str()
363    }
364
365    pub fn source_mut(&mut self) -> &mut String {
366        &mut self.source
367    }
368
369    pub fn set_source(&mut self, source: impl Into<String>) {
370        self.source = source.into();
371    }
372
373    pub fn options(&self) -> &Options {
374        &self.options
375    }
376
377    pub fn options_mut(&mut self) -> &mut Options {
378        &mut self.options
379    }
380
381    pub fn set_options(&mut self, options: Options) {
382        self.options = options;
383    }
384
385    pub fn set_option<K: Into<String>, V: Into<OptionValue>>(&mut self, key: K, value: V) {
386        self.options.insert(key, value);
387    }
388
389    pub fn resource(&self) -> &str {
390        self.resource.as_str()
391    }
392
393    pub fn resource_mut(&mut self) -> &mut String {
394        &mut self.resource
395    }
396
397    pub fn set_resource(&mut self, resource: impl Into<String>) {
398        self.resource = resource.into();
399        if !self.resource.is_empty() {
400            self.resource_colon = true;
401        }
402    }
403
404    pub fn skip_errors(&self) -> bool {
405        self.skip_errors
406    }
407
408    pub fn set_skip_errors(&mut self, skip_errors: bool) {
409        self.skip_errors = skip_errors;
410    }
411
412    pub fn resource_colon(&self) -> bool {
413        self.resource_colon
414    }
415
416    pub fn set_resource_colon(&mut self, resource_colon: bool) {
417        self.resource_colon = resource_colon;
418    }
419}
420
421/// Builds a [`Source`].
422#[derive(Debug, Clone, PartialEq, Default)]
423pub struct SourceBuilder {
424    source: Option<String>,
425    options: Options,
426    resource: String,
427    skip_errors: bool,
428    resource_colon: bool,
429}
430
431impl SourceBuilder {
432    pub fn new() -> Self {
433        Self::default()
434    }
435
436    pub fn with_source(mut self, source: impl Into<String>) -> Self {
437        self.source = Some(source.into());
438        self
439    }
440
441    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
442        self.resource = resource.into();
443        self
444    }
445
446    pub fn with_options(mut self, options: Options) -> Self {
447        self.options = options;
448        self
449    }
450
451    pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
452        self.options.insert(key, value);
453        self
454    }
455
456    pub fn with_skip_errors(mut self, skip_errors: bool) -> Self {
457        self.skip_errors = skip_errors;
458        self
459    }
460
461    pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
462        self.resource_colon = resource_colon;
463        self
464    }
465
466    pub fn build(self) -> Result<Source, Error> {
467        let source = self.source.ok_or(Error::MissingSource)?;
468        if source.is_empty() {
469            return Err(Error::MissingSource);
470        }
471        let resource_colon = self.resource_colon || !self.resource.is_empty();
472        Ok(Source {
473            source,
474            options: self.options,
475            resource: self.resource,
476            skip_errors: self.skip_errors,
477            resource_colon,
478        })
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn builder_requires_source() {
488        let error = SourceBuilder::new().build().unwrap_err();
489        assert!(matches!(error, Error::MissingSource));
490
491        let error = SourceBuilder::new().with_source("").build().unwrap_err();
492        assert!(matches!(error, Error::MissingSource));
493    }
494
495    #[test]
496    fn builder_with_option_and_into_string() {
497        let source = SourceBuilder::new()
498            .with_source("env")
499            .with_resource("")
500            .with_option("prefix", "APP")
501            .with_option("timeout", 30_i64)
502            .with_option("retry", true)
503            .build()
504            .unwrap();
505
506        assert_eq!(source.source(), "env");
507        assert_eq!(source.resource(), "");
508        assert_eq!(
509            source.options().get("prefix"),
510            Some(&OptionValue::String("APP".into()))
511        );
512        assert_eq!(
513            source.options().get("timeout"),
514            Some(&OptionValue::Integer(30))
515        );
516        assert_eq!(
517            source.options().get("retry"),
518            Some(&OptionValue::Bool(true))
519        );
520    }
521
522    #[test]
523    fn options_last_key_wins() {
524        let mut options = Options::new();
525        options.insert("prefix", "OLD");
526        options.insert("prefix", "NEW");
527        assert_eq!(options.len(), 1);
528        assert_eq!(
529            options.get("prefix"),
530            Some(&OptionValue::String("NEW".into()))
531        );
532    }
533
534    #[test]
535    fn option_value_accessors_and_type_name() {
536        let value = OptionValue::from(vec!["a", "b"]);
537        assert!(value.is_list());
538        assert_eq!(value.type_name(), OptionValueType::List);
539        assert_eq!(value.as_list().unwrap().len(), 2);
540
541        let mut map = OptionValue::new_map();
542        map.map_mut()
543            .unwrap()
544            .insert("enabled", OptionValue::from(true));
545        assert_eq!(map.type_name(), OptionValueType::Map);
546    }
547
548    #[test]
549    fn config_source_setters() {
550        let mut source = SourceBuilder::new()
551            .with_source("file")
552            .with_resource("/etc/app")
553            .build()
554            .unwrap();
555
556        source.set_source("http");
557        source.set_resource("https://example.com/config.json");
558        source.set_option("timeout", 5_u32);
559
560        assert_eq!(source.source(), "http");
561        assert_eq!(source.resource(), "https://example.com/config.json");
562        assert_eq!(
563            source.options().get("timeout"),
564            Some(&OptionValue::Integer(5))
565        );
566    }
567}