stackedconfig/
lib.rs

1#[macro_use]
2extern crate serde_json;
3
4use serde_json::{Value};
5
6/// Combines multiple `serde_json::Value` objects together so they can be
7/// queried as a single, nested object.
8#[derive(Debug)]
9pub struct ConfigStack {
10    path_sep: char,
11    configs: Vec<Value>,
12}
13
14/// Return value from looking up a path from a ConfigStack
15///
16///     conf.get("/foo/bar")      // Lookup::Found(1)
17///     conf.get("/foo/bar/baz")  // Lookup::Missing
18#[derive(Debug, PartialEq)]
19pub enum Lookup {
20    /// Indicates that the path did not resolve to a value
21    Missing,
22    /// Contains the `serde_json::Value` found from a lookup
23    Found(Value),
24}
25
26impl ConfigStack {
27    pub fn new() -> ConfigStack {
28        ConfigStack { path_sep: '/', configs: vec![], }
29    }
30
31    pub fn with_path_sep(self, sep: char) -> ConfigStack {
32        ConfigStack {
33            path_sep: sep,
34            configs: self.configs,
35        }
36    }
37
38    pub fn push(mut self, config: Value) -> ConfigStack {
39        self.configs.push(config);
40        self
41    }
42
43    pub fn pop(mut self) -> Option<Value> {
44        self.configs.pop()
45    }
46
47    fn path_to_parts(&self, path: &str) -> Vec<String> {
48        let parts = path.trim_matches(self.path_sep).split(self.path_sep);
49        parts.map(|s| s.to_string()).collect()
50    }
51
52    pub fn get(&self, path: &str) -> Lookup {
53        self.get_parts(self.path_to_parts(path))
54    }
55
56    pub fn get_parts(&self, path_parts: Vec<String>) -> Lookup {
57        'outer: for i in 0..self.configs.len() {
58            let idx = self.configs.len() - i - 1;
59            let mut node = &self.configs[idx];
60
61            for part in path_parts.iter() {
62                match node.get(part) {
63                    Some(subobj) => node = subobj,
64                    None => {
65                        if idx == 0 {
66                            return Lookup::Missing;
67                        }
68                        continue 'outer;
69                    },
70                }
71            }
72            return Lookup::Found(node.to_owned());
73        }
74        return Lookup::Missing;
75    }
76}
77
78
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_lookup() {
84        fn work() -> serde_json::Result<ConfigStack> {
85            let stack = ConfigStack::new();
86
87            let s1 = r#"{
88                "a": {
89                    "b": {
90                        "c": 1,
91                        "d": [1, 2, 3]
92                    },
93                    "e": 1
94                }
95            }"#;
96            let v1: Value = serde_json::from_str(s1)?;
97
98            let s2 = r#"{
99                "a": {
100                    "b": {
101                        "c": 2
102                    }
103                }
104            }"#;
105            let v2: Value = serde_json::from_str(s2)?;
106            Ok(stack.push(v1).push(v2))
107        }
108
109        match work() {
110            Ok(stack) => {
111                let expected: Value = json!(2);
112                assert_eq!(stack.get("a/b/c"), Lookup::Found(expected));
113            },
114            _ => assert!(false),
115        }
116    }
117}