lucet_module_data/
bindings.rs

1use failure::{format_err, Error};
2use serde_json::{self, Map, Value};
3use std::collections::{hash_map::Entry, HashMap};
4use std::fs;
5use std::path::Path;
6
7#[derive(Debug, Clone)]
8pub struct Bindings {
9    bindings: HashMap<String, HashMap<String, String>>,
10}
11
12impl Bindings {
13    pub fn new(bindings: HashMap<String, HashMap<String, String>>) -> Bindings {
14        Self { bindings: bindings }
15    }
16
17    pub fn env(env: HashMap<String, String>) -> Bindings {
18        let mut bindings = HashMap::new();
19        bindings.insert("env".to_owned(), env);
20        Self::new(bindings)
21    }
22
23    pub fn empty() -> Bindings {
24        Self::new(HashMap::new())
25    }
26
27    pub fn from_json(v: &Value) -> Result<Bindings, Error> {
28        match v.as_object() {
29            Some(modules) => Self::parse_modules_json_obj(modules),
30            None => Err(format_err!("top level json expected to be object"))?,
31        }
32    }
33
34    pub fn from_str(s: &str) -> Result<Bindings, Error> {
35        let top: Value = serde_json::from_str(s)?;
36        Ok(Self::from_json(&top)?)
37    }
38
39    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Bindings, Error> {
40        let contents = fs::read_to_string(path.as_ref())?;
41        Ok(Self::from_str(&contents)?)
42    }
43
44    pub fn extend(&mut self, other: &Bindings) -> Result<(), Error> {
45        for (modname, othermodbindings) in other.bindings.iter() {
46            match self.bindings.entry(modname.clone()) {
47                Entry::Occupied(mut e) => {
48                    let existing = e.get_mut();
49                    for (bindname, binding) in othermodbindings {
50                        match existing.entry(bindname.clone()) {
51                            Entry::Vacant(e) => {
52                                e.insert(binding.clone());
53                            }
54                            Entry::Occupied(e) => {
55                                if binding != e.get() {
56                                    Err(format_err!(
57                                        "cannot re-bind {} from {} to {}",
58                                        e.key(),
59                                        binding,
60                                        e.get()
61                                    ))?;
62                                }
63                            }
64                        }
65                    }
66                }
67                Entry::Vacant(e) => {
68                    e.insert(othermodbindings.clone());
69                }
70            }
71        }
72        Ok(())
73    }
74
75    pub fn translate(&self, module: &str, symbol: &str) -> Result<String, Error> {
76        match self.bindings.get(module) {
77            Some(m) => match m.get(symbol) {
78                Some(s) => Ok(s.clone()),
79                None => Err(format_err!("Unknown symbol `{}::{}`", module, symbol)),
80            },
81            None => Err(format_err!(
82                "Unknown module for symbol `{}::{}`",
83                module,
84                symbol
85            )),
86        }
87    }
88
89    fn parse_modules_json_obj(m: &Map<String, Value>) -> Result<Self, Error> {
90        let mut res = HashMap::new();
91        for (modulename, values) in m {
92            match values.as_object() {
93                Some(methods) => {
94                    let methodmap = Self::parse_methods_json_obj(methods)?;
95                    res.insert(modulename.to_owned(), methodmap);
96                }
97                None => Err(format_err!(""))?,
98            }
99        }
100        Ok(Self::new(res))
101    }
102
103    fn parse_methods_json_obj(m: &Map<String, Value>) -> Result<HashMap<String, String>, Error> {
104        let mut res = HashMap::new();
105        for (method, i) in m {
106            match i.as_str() {
107                Some(importbinding) => {
108                    res.insert(method.to_owned(), importbinding.to_owned());
109                }
110                None => Err(format_err!(""))?,
111            }
112        }
113        Ok(res)
114    }
115
116    pub fn to_string(&self) -> Result<String, Error> {
117        let s = serde_json::to_string(&self.to_json())?;
118        Ok(s)
119    }
120
121    pub fn to_json(&self) -> Value {
122        Value::from(self.serialize_modules_json_obj())
123    }
124
125    fn serialize_modules_json_obj(&self) -> Map<String, Value> {
126        let mut m = Map::new();
127        for (modulename, values) in self.bindings.iter() {
128            m.insert(
129                modulename.to_owned(),
130                Value::from(Self::serialize_methods_json_obj(values)),
131            );
132        }
133        m
134    }
135
136    fn serialize_methods_json_obj(methods: &HashMap<String, String>) -> Map<String, Value> {
137        let mut m = Map::new();
138        for (methodname, symbol) in methods.iter() {
139            m.insert(methodname.to_owned(), Value::from(symbol.to_owned()));
140        }
141        m
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    fn test_file(f: &str) -> PathBuf {
148        PathBuf::from(format!("tests/bindings/{}", f))
149    }
150
151    use super::Bindings;
152    use std::collections::HashMap;
153    use std::path::PathBuf;
154
155    #[test]
156    fn explicit() {
157        let mut explicit_map = HashMap::new();
158        explicit_map.insert(String::from("hello"), String::from("goodbye"));
159        let map = Bindings::env(explicit_map);
160
161        let result = map.translate("env", "hello").unwrap();
162        assert!(result == "goodbye");
163
164        let result = map.translate("env", "nonexistent");
165        if let Ok(_) = result {
166            assert!(
167                false,
168                "explicit import map returned value for non-existent symbol"
169            )
170        }
171    }
172
173    #[test]
174    fn explicit_from_nonexistent_file() {
175        let fail_map = Bindings::from_file(&test_file("nonexistent_bindings.json"));
176        assert!(
177            fail_map.is_err(),
178            "ImportMap::explicit_from_file did not fail on a non-existent file"
179        );
180    }
181
182    #[test]
183    fn explicit_from_garbage_file() {
184        let fail_map = Bindings::from_file(&test_file("garbage.json"));
185        assert!(
186            fail_map.is_err(),
187            "ImportMap::explicit_from_file did not fail on a garbage file"
188        );
189    }
190
191    #[test]
192    fn explicit_from_file() {
193        let map = Bindings::from_file(&test_file("bindings_test.json"))
194            .expect("load valid bindings from file");
195        let result = map.translate("env", "hello").expect("hello has a binding");
196        assert!(result == "json is cool");
197
198        assert!(
199            map.translate("env", "nonexistent").is_err(),
200            "bindings from file returned value for non-existent symbol"
201        );
202    }
203}