diff --git a/src/engine.rs b/src/engine.rs index 2585155..1f66ec1 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -208,6 +208,52 @@ impl Engine { &self.modules } + /// Evaluate rule(s) at given path. + /// + /// [`eval_rule`] is often faster than [`eval_query`] and should be preferred if + /// OPA style [`QueryResults`] are not needed. + /// + /// ``` + /// # use regorus::*; + /// # fn main() -> anyhow::Result<()> { + /// let mut engine = Engine::new(); + /// + /// // Add policy + /// engine.add_policy( + /// "policy.rego".to_string(), + /// r#" + /// package example + /// import rego.v1 + /// + /// x = [1, 2] + /// + /// y := 5 if input.a > 2 + /// "#.to_string())?; + /// + /// // Evaluate rule. + /// let v = engine.eval_rule("data.example.x".to_string())?; + /// assert_eq!(v, Value::from(vec![Value::from(1), Value::from(2)])); + /// + /// // y evaluates to undefined. + /// let v = engine.eval_rule("data.example.y".to_string())?; + /// assert_eq!(v, Value::Undefined); + /// + /// // Evaluating a non-existent rule is an error. + /// let r = engine.eval_rule("data.exaample.x".to_string()); + /// assert!(r.is_err()); + /// + /// // Path must be valid rule paths. + /// assert!( engine.eval_rule("data".to_string()).is_err()); + /// assert!( engine.eval_rule("data.example".to_string()).is_err()); + /// # Ok(()) + /// # } + /// ``` + pub fn eval_rule(&mut self, path: String) -> Result { + self.prepare_for_eval(false)?; + self.interpreter.clean_internal_evaluation_state(); + self.interpreter.eval_rule_in_path(path) + } + /// Evaluate a Rego query. /// /// ``` @@ -419,7 +465,7 @@ impl Engine { } #[doc(hidden)] - pub fn eval_rule( + pub fn eval_rule_in_module( &mut self, module: &Ref, rule: &Ref, diff --git a/src/interpreter.rs b/src/interpreter.rs index 136fe84..a662778 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -14,7 +14,7 @@ use crate::{Expression, Extension, Location, QueryResult, QueryResults}; use anyhow::{anyhow, bail, Result}; use std::collections::btree_map::Entry as BTreeMapEntry; -use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap}; +use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}; use std::ops::Bound::*; use std::str::FromStr; @@ -75,6 +75,7 @@ pub struct Interpreter { gather_prints: bool, prints: Vec, + rule_paths: HashSet, } impl Default for Interpreter { @@ -195,6 +196,7 @@ impl Interpreter { gather_prints: false, prints: Vec::default(), + rule_paths: HashSet::new(), } } @@ -3426,6 +3428,9 @@ impl Interpreter { let comps: Vec<&str> = comps.iter().map(|s| s.text()).collect(); for c in 0..comps.len() { let path = self.current_module_path.clone() + "." + &comps[0..c + 1].join("."); + if c + 1 == comps.len() { + self.rule_paths.insert(path.clone()); + } match self.rules.entry(path) { Entry::Occupied(o) => { @@ -3450,6 +3455,10 @@ impl Interpreter { let comps: Vec<&str> = comps.iter().map(|s| s.text()).collect(); for (idx, c) in (0..comps.len()).enumerate() { let path = self.current_module_path.clone() + "." + &comps[0..c + 1].join("."); + if c + 1 == comps.len() { + self.rule_paths.insert(path.clone()); + } + match self.default_rules.entry(path) { Entry::Occupied(o) => { if idx + 1 == comps.len() { @@ -3707,4 +3716,14 @@ impl Interpreter { pub fn take_prints(&mut self) -> Result> { Ok(std::mem::take(&mut self.prints)) } + + pub fn eval_rule_in_path(&mut self, path: String) -> Result { + if !self.rule_paths.contains(&path) { + bail!("not a valid rule path"); + } + self.ensure_rule_evaluated(path.clone())?; + let parts: Vec<&str> = path.split('.').collect(); + + Ok(Self::get_value_chained(self.data.clone(), &parts[1..])) + } }