use std::collections::BTreeSet;
use dataflow_rs::datalogic_rs;
use serde_json::{Value, json};
use super::dataflow::{self, Reads};
use super::operators;
#[derive(Debug, Clone)]
pub struct Expr {
pub value: Value,
pub constant: Option<Value>,
pub compiles: bool,
pub reads: Reads,
pub operators: BTreeSet<String>,
}
impl Expr {
pub fn is_constant_falsy(&self) -> bool {
matches!(self.constant, Some(Value::Bool(false)) | Some(Value::Null))
}
pub fn is_constant_true(&self) -> bool {
matches!(self.constant, Some(Value::Bool(true)))
}
pub fn nondeterministic(&self) -> bool {
self.operators
.iter()
.any(|op| operators::NONDETERMINISTIC.contains(&op.as_str()))
}
}
pub struct Evaluator {
engine: datalogic_rs::Engine,
}
impl Default for Evaluator {
fn default() -> Self {
Self::new()
}
}
impl Evaluator {
pub fn new() -> Self {
Self {
engine: crate::engine::operators::add_to_datalogic(datalogic_rs::Engine::builder())
.build(),
}
}
pub fn expr(&self, value: &Value) -> Expr {
let compiled = self.engine.compile(value).ok();
let constant = compiled
.as_ref()
.filter(|logic| logic.is_constant())
.and_then(|_| self.evaluate(value, &json!({})));
let mut ops = BTreeSet::new();
collect_operators(value, &mut ops);
Expr {
value: value.clone(),
constant,
compiles: compiled.is_some(),
reads: dataflow::reads(value),
operators: ops,
}
}
pub fn evaluate(&self, value: &Value, context: &Value) -> Option<Value> {
self.engine.eval_into::<Value, _, _>(value, context).ok()
}
}
fn collect_operators(value: &Value, out: &mut BTreeSet<String>) {
match value {
Value::Array(items) => items.iter().for_each(|v| collect_operators(v, out)),
Value::Object(map) => {
if map.len() == 1 {
let key = map.keys().next().expect("one member");
if crate::engine::operators::is_operator(key) {
out.insert(key.clone());
}
}
map.values().for_each(|v| collect_operators(v, out));
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constants_are_the_compilers_verdict() {
let ev = Evaluator::new();
assert!(ev.expr(&json!(false)).is_constant_falsy());
assert!(ev.expr(&json!(true)).is_constant_true());
assert!(ev.expr(&json!({"==": [1, 2]})).is_constant_falsy());
assert!(
ev.expr(&json!({"and": [true, {"==": [1, 1]}]}))
.is_constant_true()
);
assert!(!ev.expr(&json!({"var": "data.x"})).is_constant_falsy());
assert_eq!(ev.expr(&json!({"var": "data.x"})).constant, None);
}
#[test]
fn evaluation_runs_the_real_engine_with_orion_operators() {
let ev = Evaluator::new();
assert_eq!(
ev.evaluate(
&json!({"==": [{"var": "data.type"}, "order"]}),
&json!({"data": {}})
),
Some(json!(false))
);
assert_eq!(
ev.evaluate(&json!({"!": {"var": "data.x"}}), &json!({"data": {}})),
Some(json!(true))
);
assert_eq!(
ev.evaluate(&json!({"hex_encode": ["a"]}), &json!({})),
Some(json!("61")),
"Orion's own operators are registered"
);
}
#[test]
fn nondeterminism_is_seen_at_any_depth() {
let ev = Evaluator::new();
assert!(
ev.expr(&json!({">": [{"var": "data.t"}, {"now": []}]}))
.nondeterministic()
);
assert!(
!ev.expr(&json!({">": [{"var": "data.t"}, 1]}))
.nondeterministic()
);
}
}