hecate 0.62.0

OpenStreetMap Inspired Data Storage Backend Focused on Performance and GeoJSON Interchange
use serde_json;
use std::fmt;
use osm::*;

#[derive(Debug, Clone, PartialEq)]
pub struct Node {
    pub id: Option<i64>,
    pub lat: Option<f32>,
    pub user: Option<String>,
    pub action: Option<Action>,
    pub uid: Option<i32>,
    pub lon: Option<f32>,
    pub modified: bool,
    pub tags: serde_json::Map<String, serde_json::Value>,
    pub version: Option<i32>,
    pub parents: Vec<i64>
}

impl Generic for Node {
    fn new() -> Node {
        Node {
            id: None,
            lat: None,
            lon: None,
            user: None,
            uid: None,
            modified: false,
            action: None,
            tags: serde_json::Map::new(),
            parents: Vec::new(),
            version: None
        }
    }

    fn value(&self) -> Value {
        Value::Node
    }

    fn set_tag(&mut self, k: String, v: String) {
        let v_unescape = unescape(v);
        
        let value = match serde_json::from_str::<serde_json::Value>(&*v_unescape) {
            Ok(value) => value,
            Err(_) => serde_json::Value::String(v_unescape)
        };
        self.tags.insert(k, value);
    }

    fn has_tags(&self) -> bool {
        !self.tags.is_empty()
    }

    fn to_feat(&self, _tree: &OSMTree) -> Result<geojson::Feature, XMLError> {
        let mut foreign = serde_json::Map::new();

        match self.is_valid() {
            Err(err) => { return Err(XMLError::InvalidNode(err)); },
            _ => ()
        }

        foreign.insert(String::from("action"), serde_json::Value::String(match self.action {
            Some(Action::Create) => String::from("create"),
            Some(Action::Modify) => String::from("modify"),
            Some(Action::Delete) => String::from("delete"),
            _ => { return Err(XMLError::InvalidNode(String::from("Missing or invalid action"))); }
        }));

        foreign.insert(String::from("version"), json!(self.version));

        let mut geom: Option<geojson::Geometry> = None;

        if self.action != Some(Action::Delete) {
            let mut coords: Vec<f64> = Vec::new();
            coords.push(self.lon.unwrap() as f64);
            coords.push(self.lat.unwrap() as f64);

            geom = Some(geojson::Geometry::new(
                geojson::Value::Point(coords)
            ));
        }

        let id: Option<geojson::feature::Id> = match self.id {
            None => None,
            Some(ref id) => Some(geojson::feature::Id::Number(serde_json::Number::from(id.clone())))
        };

        Ok(geojson::Feature {
            bbox: None,
            geometry: geom,
            id: id,
            properties: Some(self.tags.clone()),
            foreign_members: Some(foreign)
        })
    }

    fn is_valid(&self) -> Result<bool, String> {
        if self.id == None {
            return Err(String::from("Missing id"));
        }

        if self.action != Some(Action::Delete) {
            if self.lat == None {
                return Err(String::from("Missing lat"));
            }

            if self.lon == None {
                return Err(String::from("Missing lon"));
            }
        }

        if self.version == None {
            return Err(String::from("Missing version"));
        }

        return Ok(true);
    }
}

impl fmt::Display for Node {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[Node: id={}]", match self.id {
            None => String::from("None"),
            Some(ref id) => id.to_string()
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn empty() {
        let mut n = Node::new();
        assert_eq!(n.id, None);
        assert_eq!(n.lat, None);
        assert_eq!(n.lon, None);
        assert_eq!(n.user, None);
        assert_eq!(n.uid, None);
        assert_eq!(n.modified, false);
        assert_eq!(n.action, None);
        assert_eq!(n.tags.is_empty(), true);
        assert_eq!(n.parents.len(), 0);
        assert_eq!(n.version, None);

        assert_eq!(n.value(), Value::Node);
        assert_eq!(n.is_valid().is_err(), true);

        assert_eq!(format!("{}", n), "[Node: id=None]");

        n.id = Some(1);
        assert_eq!(format!("{}", n), "[Node: id=1]");

        n.id = Some(-1);
        assert_eq!(format!("{}", n), "[Node: id=-1]");
    }

    #[test]
    fn tags() {
        let mut n = Node::new();
        assert_eq!(n.has_tags(), false);
        n.set_tag(String::from("hello"), String::from("world"));
        assert_eq!(n.has_tags(), true);

        assert_eq!(format!("{}", n), "[Node: id=None]");
    }

    #[test]
    fn validity() {
        let mut n = Node::new();
        assert_eq!(n.is_valid().is_err(), true);
        n.id = Some(1);
        assert_eq!(n.is_valid().is_err(), true);
        n.lat = Some(1.1);
        assert_eq!(n.is_valid().is_err(), true);
        n.lon = Some(2.2);
        assert_eq!(n.is_valid().is_err(), true);
        n.version = Some(1);
        assert_eq!(n.is_valid().is_err(), false);
    }

    #[test]
    fn to_feat() {
        let mut n = Node::new();
        let tree = OSMTree::new();
        assert_eq!(n.to_feat(&tree).err(), Some(XMLError::InvalidNode(String::from("Missing id"))), "missing id errors");

        n.id = Some(1);
        assert_eq!(n.to_feat(&tree).err(), Some(XMLError::InvalidNode(String::from("Missing lat"))), "missing lat errors");

        n.lat = Some(1.1);
        assert_eq!(n.to_feat(&tree).err(), Some(XMLError::InvalidNode(String::from("Missing lon"))), "missing lon errors");

        n.lon = Some(2.2);
        assert_eq!(n.to_feat(&tree).err(), Some(XMLError::InvalidNode(String::from("Missing version"))), "missing version errors");

        n.version = Some(1);
        assert_eq!(n.to_feat(&tree).err(), Some(XMLError::InvalidNode(String::from("Missing or invalid action"))), "missing/invalid action errors");

        n.action = Some(Action::Create);

        let mut fmem = serde_json::Map::new();
        fmem.insert(String::from("action"), json!(String::from("create")));
        fmem.insert(String::from("version"), json!(1));

        assert_eq!(n.to_feat(&tree).ok(), Some(geojson::Feature {
            bbox: None,
            id: Some(geojson::feature::Id::Number(serde_json::Number::from(1))),
            properties: Some(serde_json::Map::new()),
            geometry: Some(geojson::Geometry::new(geojson::Value::Point(vec!(2.200000047683716, 1.100000023841858)))),
            foreign_members: Some(fmem.clone())
        }), "action create eq");

        n.action = Some(Action::Modify);
        fmem.insert(String::from("action"), json!(String::from("modify")));
        assert_eq!(n.to_feat(&tree).ok(), Some(geojson::Feature {
            bbox: None,
            id: Some(geojson::feature::Id::Number(serde_json::Number::from(1))),
            properties: Some(serde_json::Map::new()),
            geometry: Some(geojson::Geometry::new(geojson::Value::Point(vec!(2.200000047683716, 1.100000023841858)))),
            foreign_members: Some(fmem.clone())
        }), "action modify eq");

        n.action = Some(Action::Delete);
        fmem.insert(String::from("action"), json!(String::from("delete")));
        assert_eq!(n.to_feat(&tree).ok(), Some(geojson::Feature {
            bbox: None,
            id: Some(geojson::feature::Id::Number(serde_json::Number::from(1))),
            properties: Some(serde_json::Map::new()),
            geometry: None,
            foreign_members: Some(fmem.clone())
        }), "action delete eq");
    }
}