wof 1.0.0

The Who's On First rust library and command line.
Documentation
use super::Predicate;
use crate::utils::JsonUtils;
use crate::wof::WOFGeoJSON;
use crate::JsonValue;

pub trait Evaluate {
  fn eval(&self, predicate: &Predicate) -> Result<Predicate, String> {
    match predicate {
      Predicate::And(left, right) => Ok(Predicate::Boolean(
        self.eval(&left)?.as_bool()? && self.eval(&right)?.as_bool()?,
      )),
      Predicate::Or(left, right) => Ok(Predicate::Boolean(
        self.eval(&left)?.as_bool()? || self.eval(&right)?.as_bool()?,
      )),
      Predicate::Eq(left, right) => Ok(Predicate::Boolean(self.eval(&left)? == self.eval(&right)?)),
      Predicate::Not(predicate) => Ok(Predicate::Boolean(!(self.eval(&predicate)?.as_bool()?))),
      Predicate::Boolean(b) => Ok(Predicate::Boolean(b == &true)),
      Predicate::Variable(s) => self.get_variable_value(s),
      _ => Ok(predicate.clone()),
    }
  }

  fn get_variable_value(&self, key: &String) -> Result<Predicate, String> {
    match key.as_str() {
      "geom_type" => self.get_geometry_type(),
      key => self.get_property(key),
    }
  }
  fn get_geometry_type(&self) -> Result<Predicate, String>;
  fn get_property(&self, key: &str) -> Result<Predicate, String>;
}

impl Evaluate for WOFGeoJSON<'_> {
  fn get_geometry_type(&self) -> Result<Predicate, String> {
    self
      .geometry
      .get("type")
      .ok_or(format!("Evaluated json must contains geometry.type"))
      .map(|value| {
        value
          .as_str()
          .map(|value| Predicate::String(value.to_string()))
          .unwrap_or(Predicate::Null)
      })
  }

  fn get_property(&self, key: &str) -> Result<Predicate, String> {
    self
      .properties
      .get(key)
      .map_or(Ok(Predicate::Null), self::json_value_to_predicate)
  }
}

impl Evaluate for JsonValue {
  fn get_geometry_type(&self) -> Result<Predicate, String> {
    self
      .as_object()
      .ok_or(format!("Evaluated json must be an object!"))?
      .get("geometry")
      .ok_or(format!("Evaluated json must contains a geometry object!"))?
      .as_object()
      .ok_or(format!("Evaluated json geometry must be an object!"))?
      .get("type")
      .ok_or(format!("Evaluated json must contains geometry.type"))
      .map(|value| {
        value
          .as_str()
          .map(|value| Predicate::String(value.to_string()))
          .unwrap_or(Predicate::Null)
      })
  }

  fn get_property(&self, key: &str) -> Result<Predicate, String> {
    self
      .as_object()
      .ok_or(format!("Evaluated json must be an object!"))?
      .get("properties")
      .ok_or(format!("Evaluated json must contains a properties object!"))?
      .as_object()
      .ok_or(format!("Evaluated json properties must be an object!"))?
      .get(key)
      .map_or(Ok(Predicate::Null), self::json_value_to_predicate)
  }
}

fn json_value_to_predicate(value: &JsonValue) -> Result<Predicate, String> {
  match value {
    JsonValue::Short(s) => Ok(Predicate::String(s.to_string())),
    JsonValue::String(s) => Ok(Predicate::String(s.to_string())),
    JsonValue::Number(_) => Ok(Predicate::Number(value.as_f64().unwrap())),
    JsonValue::Boolean(b) => Ok(Predicate::Boolean(*b)),
    _ => Ok(Predicate::Null),
  }
}

#[cfg(test)]
mod test_expression {
  use super::*;
  use json::object;
  use std::convert::TryFrom;

  #[test]
  fn evaluate_predicate() -> Result<(), String> {
    let json = object! {
      "type" => "Feature",
      "properties" => object!{
        "name:fra_x_preferred" => vec![ "Ajaccio" ],
        "wof:id" => 101748927,
        "wof:lang" => vec![ "fre" ],
        "name:eng_x_preferred" => vec![ "Ajaccio" ],
        "wof:placetype" => "localadmin",
        "bool_true" => true,
      },
      "geometry" => object!{
        "coordinates" => vec![vec![
          vec![8.585396,41.873571], vec![8.826011,41.873571], vec![8.826011,41.971536], vec![8.585396,41.968222], vec![8.585396,41.873571]
        ]],
        "type" => "Polygon"
      },
      "bbox" => vec![
        8.585396,
        41.873571,
        8.826011,
        41.971536
      ],
      "id" => 101748927,
    };
    let wof_obj = WOFGeoJSON::as_valid_wof_geojson(&json)?;

    assert_eq!(
      wof_obj.eval(&Predicate::try_from(format!("geom_type = 'Polygon'"))?)?,
      Predicate::Boolean(true)
    );
    assert_eq!(
      json.eval(&Predicate::try_from(format!("geom_type = 'Polygon'"))?)?,
      Predicate::Boolean(true)
    );

    assert_eq!(
      wof_obj.eval(&Predicate::try_from(format!(
        "wof:placetype = 'localadmin'"
      ))?)?,
      Predicate::Boolean(true)
    );
    assert_eq!(
      json.eval(&Predicate::try_from(format!(
        "wof:placetype = 'localadmin'"
      ))?)?,
      Predicate::Boolean(true)
    );

    assert_eq!(
      wof_obj.eval(&Predicate::try_from(format!("wof:id = 101748927"))?)?,
      Predicate::Boolean(true)
    );
    assert_eq!(
      json.eval(&Predicate::try_from(format!("wof:id = 101748927"))?)?,
      Predicate::Boolean(true)
    );

    assert_eq!(
      wof_obj.eval(&Predicate::try_from(format!("geom:src = 'osm'"))?)?,
      Predicate::Boolean(false)
    );
    assert_eq!(
      json.eval(&Predicate::try_from(format!("geom:src = 'osm'"))?)?,
      Predicate::Boolean(false)
    );

    assert_eq!(
      wof_obj.eval(&Predicate::try_from(format!(
        "wof:placetype = wof:placetype"
      ))?)?,
      Predicate::Boolean(true)
    );
    assert_eq!(
      json.eval(&Predicate::try_from(format!(
        "wof:placetype = wof:placetype"
      ))?)?,
      Predicate::Boolean(true)
    );

    Ok(())
  }
}