eval_rule: Evaluate rules directly instead of queries (#186)

closes #185

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2024-03-26 05:24:21 +05:30
committed by GitHub
parent 330a6dff72
commit 3d98c3b12e
2 changed files with 67 additions and 2 deletions

View File

@@ -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<Value> {
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<Module>,
rule: &Ref<Rule>,

View File

@@ -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<String>,
rule_paths: HashSet<String>,
}
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<Vec<String>> {
Ok(std::mem::take(&mut self.prints))
}
pub fn eval_rule_in_path(&mut self, path: String) -> Result<Value> {
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..]))
}
}