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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#[macro_use]
extern crate serde_json;

use serde_json::{Value};

/// Combines multiple `serde_json::Value` objects together so they can be
/// queried as a single, nested object.
///
/// Supports using a custom separator if desired for path style queries.
/// Defaults to a forward slash `/`.
#[derive(Debug)]
pub struct ConfigStack {
    path_sep: char,
    configs: Vec<Value>,
}

/// Return value from looking up a path from a [`ConfigStack`]
///
///     conf.get("/foo/bar")      // Lookup::Found(1)
///     conf.get("/foo/bar/baz")  // Lookup::Missing
///
/// [`ConfigStack`]: ./struct.ConfigStack.html
#[derive(Debug, PartialEq)]
pub enum Lookup {
    /// Indicates that the path did not resolve to a value
    Missing,
    /// Contains the `serde_json::Value` found from a lookup
    Found(Value),
}

impl ConfigStack {
    /// Create a new `ConfigStack`
    pub fn new() -> ConfigStack {
        ConfigStack { path_sep: '/', configs: vec![], }
    }

    /// Returns a new `ConfigStack` with the given separator
    ///
    /// ```rust
    /// let conf = ConfigStack::new().with_path_sep('.').push(v1).push(v2);
    /// let val = conf.get("foo.bar.baz");
    /// ```
    pub fn with_path_sep(self, sep: char) -> ConfigStack {
        ConfigStack {
            path_sep: sep,
            configs: self.configs,
        }
    }

    /// Adds a new configuration value to the stack.  The added value becomes
    /// the highest priority value on the stack.
    pub fn push(mut self, config: Value) -> ConfigStack {
        self.configs.push(config);
        self
    }

    /// Removes the top level configuration from the stack and returns it. The
    /// opposite of `push`.  If no configurations are on the stack then `None`
    /// is returned.
    pub fn pop(mut self) -> Option<Value> {
        self.configs.pop()
    }

    /// Converts a path to its constituent parts using the current path
    /// separator. Trims any leading or trailing separators
    fn path_to_parts(&self, path: &str) -> Vec<String> {
        let parts = path.trim_matches(self.path_sep).split(self.path_sep);
        parts.map(|s| s.to_string()).collect()
    }

    /// Looks up a value at the given path
    ///
    /// ```rust
    /// conf.get("foo/bar/baz") -> Lookup::Found(Value::Bool(true))
    /// conf.get("foo/bar/qux") -> Lookup::Missing
    /// ```
    pub fn get(&self, path: &str) -> Lookup {
        self.get_parts(self.path_to_parts(path))
    }

    /// Looks up a value at the given path where the path is a `Vec<String>`. No
    /// parsing is performed on the path parts, so this method will not split on
    /// the path separator.
    pub fn get_parts(&self, path_parts: Vec<String>) -> Lookup {
        'outer: for i in 0..self.configs.len() {
            let idx = self.configs.len() - i - 1;
            let mut node = &self.configs[idx];

            for part in path_parts.iter() {
                match node.get(part) {
                    Some(subobj) => node = subobj,
                    None => {
                        if idx == 0 {
                            return Lookup::Missing;
                        }
                        continue 'outer;
                    },
                }
            }
            return Lookup::Found(node.to_owned());
        }
        return Lookup::Missing;
    }
}


mod tests {
    use super::*;

    #[test]
    fn test_lookup() {
        fn work() -> serde_json::Result<ConfigStack> {
            let stack = ConfigStack::new();

            let s1 = r#"{
                "a": {
                    "b": {
                        "c": 1,
                        "d": [1, 2, 3]
                    },
                    "e": 1
                }
            }"#;
            let v1: Value = serde_json::from_str(s1)?;

            let s2 = r#"{
                "a": {
                    "b": {
                        "c": 2
                    }
                }
            }"#;
            let v2: Value = serde_json::from_str(s2)?;
            Ok(stack.push(v1).push(v2))
        }

        match work() {
            Ok(stack) => {
                let expected: Value = json!(2);
                assert_eq!(stack.get("a/b/c"), Lookup::Found(expected));
            },
            _ => assert!(false),
        }
    }
}