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///
348/// See the [crate-level documentation](crate) for the source string format, parsing rules, and
349/// examples.
350#[derive(Debug, Clone, PartialEq)]
351pub struct Source {
352    pub(crate) source: String,
353    pub(crate) options: Options,
354    pub(crate) resource: String,
355    pub(crate) ignore_errors: bool,
356    pub(crate) resource_colon: bool,
357}
358
359impl Source {
360    pub fn parse(input: &str) -> Result<Self, ParseError> {
361        parse::parse(input)
362    }
363
364    pub fn source(&self) -> &str {
365        self.source.as_str()
366    }
367
368    pub fn source_mut(&mut self) -> &mut String {
369        &mut self.source
370    }
371
372    pub fn set_source(&mut self, source: impl Into<String>) {
373        self.source = source.into();
374    }
375
376    pub fn with_source(mut self, source: impl Into<String>) -> Self {
377        self.source = source.into();
378        self
379    }
380
381    pub fn options(&self) -> &Options {
382        &self.options
383    }
384
385    pub fn options_mut(&mut self) -> &mut Options {
386        &mut self.options
387    }
388
389    pub fn set_options(&mut self, options: Options) {
390        self.options = options;
391    }
392
393    pub fn with_options(mut self, options: Options) -> Self {
394        self.options = options;
395        self
396    }
397
398    pub fn set_option<K: Into<String>, V: Into<OptionValue>>(&mut self, key: K, value: V) {
399        self.options.insert(key, value);
400    }
401
402    pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
403        self.options.insert(key, value);
404        self
405    }
406
407    pub fn resource(&self) -> &str {
408        self.resource.as_str()
409    }
410
411    pub fn resource_mut(&mut self) -> &mut String {
412        &mut self.resource
413    }
414
415    pub fn set_resource(&mut self, resource: impl Into<String>) {
416        self.resource = resource.into();
417        if !self.resource.is_empty() {
418            self.resource_colon = true;
419        }
420    }
421
422    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
423        self.resource = resource.into();
424        if !self.resource.is_empty() {
425            self.resource_colon = true;
426        }
427        self
428    }
429
430    pub fn ignore_errors(&self) -> bool {
431        self.ignore_errors
432    }
433
434    pub fn set_ignore_errors(&mut self, ignore_errors: bool) {
435        self.ignore_errors = ignore_errors;
436    }
437
438    pub fn with_ignore_errors(mut self, ignore_errors: bool) -> Self {
439        self.ignore_errors = ignore_errors;
440        self
441    }
442
443    pub fn resource_colon(&self) -> bool {
444        self.resource_colon
445    }
446
447    pub fn set_resource_colon(&mut self, resource_colon: bool) {
448        self.resource_colon = resource_colon;
449    }
450
451    pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
452        self.resource_colon = resource_colon;
453        self
454    }
455}
456
457/// Builds a [`Source`].
458#[derive(Debug, Clone, PartialEq, Default)]
459pub struct SourceBuilder {
460    source: Option<String>,
461    options: Options,
462    resource: String,
463    ignore_errors: bool,
464    resource_colon: bool,
465}
466
467impl SourceBuilder {
468    pub fn new() -> Self {
469        Self::default()
470    }
471
472    pub fn with_source(mut self, source: impl Into<String>) -> Self {
473        self.source = Some(source.into());
474        self
475    }
476
477    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
478        self.resource = resource.into();
479        self
480    }
481
482    pub fn with_options(mut self, options: Options) -> Self {
483        self.options = options;
484        self
485    }
486
487    pub fn with_option<K: Into<String>, V: Into<OptionValue>>(mut self, key: K, value: V) -> Self {
488        self.options.insert(key, value);
489        self
490    }
491
492    pub fn with_ignore_errors(mut self, ignore_errors: bool) -> Self {
493        self.ignore_errors = ignore_errors;
494        self
495    }
496
497    pub fn with_resource_colon(mut self, resource_colon: bool) -> Self {
498        self.resource_colon = resource_colon;
499        self
500    }
501
502    pub fn build(self) -> Result<Source, Error> {
503        let source = self.source.ok_or(Error::MissingSource)?;
504        if source.is_empty() {
505            return Err(Error::MissingSource);
506        }
507        let resource_colon = self.resource_colon || !self.resource.is_empty();
508        Ok(Source {
509            source,
510            options: self.options,
511            resource: self.resource,
512            ignore_errors: self.ignore_errors,
513            resource_colon,
514        })
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn builder_requires_source() {
524        let error = SourceBuilder::new().build().unwrap_err();
525        assert!(matches!(error, Error::MissingSource));
526
527        let error = SourceBuilder::new().with_source("").build().unwrap_err();
528        assert!(matches!(error, Error::MissingSource));
529    }
530
531    #[test]
532    fn builder_with_option_and_into_string() {
533        let source = SourceBuilder::new()
534            .with_source("env")
535            .with_resource("")
536            .with_option("prefix", "APP")
537            .with_option("timeout", 30_i64)
538            .with_option("retry", true)
539            .build()
540            .unwrap();
541
542        assert_eq!(source.source(), "env");
543        assert_eq!(source.resource(), "");
544        assert_eq!(
545            source.options().get("prefix"),
546            Some(&OptionValue::String("APP".into()))
547        );
548        assert_eq!(
549            source.options().get("timeout"),
550            Some(&OptionValue::Integer(30))
551        );
552        assert_eq!(
553            source.options().get("retry"),
554            Some(&OptionValue::Bool(true))
555        );
556    }
557
558    #[test]
559    fn options_last_key_wins() {
560        let mut options = Options::new();
561        options.insert("prefix", "OLD");
562        options.insert("prefix", "NEW");
563        assert_eq!(options.len(), 1);
564        assert_eq!(
565            options.get("prefix"),
566            Some(&OptionValue::String("NEW".into()))
567        );
568    }
569
570    #[test]
571    fn option_value_accessors_and_type_name() {
572        let value = OptionValue::from(vec!["a", "b"]);
573        assert!(value.is_list());
574        assert_eq!(value.type_name(), OptionValueType::List);
575        assert_eq!(value.as_list().unwrap().len(), 2);
576
577        let mut map = OptionValue::new_map();
578        map.map_mut()
579            .unwrap()
580            .insert("enabled", OptionValue::from(true));
581        assert_eq!(map.type_name(), OptionValueType::Map);
582    }
583
584    #[test]
585    fn config_source_setters() {
586        let mut source = SourceBuilder::new()
587            .with_source("file")
588            .with_resource("/etc/app")
589            .build()
590            .unwrap();
591
592        source.set_source("http");
593        source.set_resource("https://example.com/config.json");
594        source.set_option("timeout", 5_u32);
595
596        assert_eq!(source.source(), "http");
597        assert_eq!(source.resource(), "https://example.com/config.json");
598        assert_eq!(
599            source.options().get("timeout"),
600            Some(&OptionValue::Integer(5))
601        );
602    }
603}