lucet-module-data 0.1.2

DEPRECATED: This package is now available as `lucet-module`.
Documentation
use failure::{format_err, Error};
use serde_json::{self, Map, Value};
use std::collections::{hash_map::Entry, HashMap};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone)]
pub struct Bindings {
    bindings: HashMap<String, HashMap<String, String>>,
}

impl Bindings {
    pub fn new(bindings: HashMap<String, HashMap<String, String>>) -> Bindings {
        Self { bindings: bindings }
    }

    pub fn env(env: HashMap<String, String>) -> Bindings {
        let mut bindings = HashMap::new();
        bindings.insert("env".to_owned(), env);
        Self::new(bindings)
    }

    pub fn empty() -> Bindings {
        Self::new(HashMap::new())
    }

    pub fn from_json(v: &Value) -> Result<Bindings, Error> {
        match v.as_object() {
            Some(modules) => Self::parse_modules_json_obj(modules),
            None => Err(format_err!("top level json expected to be object"))?,
        }
    }

    pub fn from_str(s: &str) -> Result<Bindings, Error> {
        let top: Value = serde_json::from_str(s)?;
        Ok(Self::from_json(&top)?)
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Bindings, Error> {
        let contents = fs::read_to_string(path.as_ref())?;
        Ok(Self::from_str(&contents)?)
    }

    pub fn extend(&mut self, other: &Bindings) -> Result<(), Error> {
        for (modname, othermodbindings) in other.bindings.iter() {
            match self.bindings.entry(modname.clone()) {
                Entry::Occupied(mut e) => {
                    let existing = e.get_mut();
                    for (bindname, binding) in othermodbindings {
                        match existing.entry(bindname.clone()) {
                            Entry::Vacant(e) => {
                                e.insert(binding.clone());
                            }
                            Entry::Occupied(e) => {
                                if binding != e.get() {
                                    Err(format_err!(
                                        "cannot re-bind {} from {} to {}",
                                        e.key(),
                                        binding,
                                        e.get()
                                    ))?;
                                }
                            }
                        }
                    }
                }
                Entry::Vacant(e) => {
                    e.insert(othermodbindings.clone());
                }
            }
        }
        Ok(())
    }

    pub fn translate(&self, module: &str, symbol: &str) -> Result<String, Error> {
        match self.bindings.get(module) {
            Some(m) => match m.get(symbol) {
                Some(s) => Ok(s.clone()),
                None => Err(format_err!("Unknown symbol `{}::{}`", module, symbol)),
            },
            None => Err(format_err!(
                "Unknown module for symbol `{}::{}`",
                module,
                symbol
            )),
        }
    }

    fn parse_modules_json_obj(m: &Map<String, Value>) -> Result<Self, Error> {
        let mut res = HashMap::new();
        for (modulename, values) in m {
            match values.as_object() {
                Some(methods) => {
                    let methodmap = Self::parse_methods_json_obj(methods)?;
                    res.insert(modulename.to_owned(), methodmap);
                }
                None => Err(format_err!(""))?,
            }
        }
        Ok(Self::new(res))
    }

    fn parse_methods_json_obj(m: &Map<String, Value>) -> Result<HashMap<String, String>, Error> {
        let mut res = HashMap::new();
        for (method, i) in m {
            match i.as_str() {
                Some(importbinding) => {
                    res.insert(method.to_owned(), importbinding.to_owned());
                }
                None => Err(format_err!(""))?,
            }
        }
        Ok(res)
    }

    pub fn to_string(&self) -> Result<String, Error> {
        let s = serde_json::to_string(&self.to_json())?;
        Ok(s)
    }

    pub fn to_json(&self) -> Value {
        Value::from(self.serialize_modules_json_obj())
    }

    fn serialize_modules_json_obj(&self) -> Map<String, Value> {
        let mut m = Map::new();
        for (modulename, values) in self.bindings.iter() {
            m.insert(
                modulename.to_owned(),
                Value::from(Self::serialize_methods_json_obj(values)),
            );
        }
        m
    }

    fn serialize_methods_json_obj(methods: &HashMap<String, String>) -> Map<String, Value> {
        let mut m = Map::new();
        for (methodname, symbol) in methods.iter() {
            m.insert(methodname.to_owned(), Value::from(symbol.to_owned()));
        }
        m
    }
}

#[cfg(test)]
mod tests {
    fn test_file(f: &str) -> PathBuf {
        PathBuf::from(format!("tests/bindings/{}", f))
    }

    use super::Bindings;
    use std::collections::HashMap;
    use std::path::PathBuf;

    #[test]
    fn explicit() {
        let mut explicit_map = HashMap::new();
        explicit_map.insert(String::from("hello"), String::from("goodbye"));
        let map = Bindings::env(explicit_map);

        let result = map.translate("env", "hello").unwrap();
        assert!(result == "goodbye");

        let result = map.translate("env", "nonexistent");
        if let Ok(_) = result {
            assert!(
                false,
                "explicit import map returned value for non-existent symbol"
            )
        }
    }

    #[test]
    fn explicit_from_nonexistent_file() {
        let fail_map = Bindings::from_file(&test_file("nonexistent_bindings.json"));
        assert!(
            fail_map.is_err(),
            "ImportMap::explicit_from_file did not fail on a non-existent file"
        );
    }

    #[test]
    fn explicit_from_garbage_file() {
        let fail_map = Bindings::from_file(&test_file("garbage.json"));
        assert!(
            fail_map.is_err(),
            "ImportMap::explicit_from_file did not fail on a garbage file"
        );
    }

    #[test]
    fn explicit_from_file() {
        let map = Bindings::from_file(&test_file("bindings_test.json"))
            .expect("load valid bindings from file");
        let result = map.translate("env", "hello").expect("hello has a binding");
        assert!(result == "json is cool");

        assert!(
            map.translate("env", "nonexistent").is_err(),
            "bindings from file returned value for non-existent symbol"
        );
    }
}