From d39200a52c5359b4d9c0642c801a3d6e8386e2aa Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:21:49 -0800 Subject: [PATCH] - Document Location, Expression, QueryResult (#109) Signed-off-by: Anand Krishnamoorthi --- README.md | 21 ++++-- src/engine.rs | 3 + src/interpreter.rs | 38 +---------- src/lib.rs | 161 ++++++++++++++++++++++++++++++++++++++++++++- src/value.rs | 18 +++++ 5 files changed, 196 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index aa6af1e..c29ce8a 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,14 @@ **Regorus** is - - *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) interpreter written in Rust. + - *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) + interpreter written in Rust. - *Rigorous* - A rigorous enforcer of well-defined Rego semantics. Regorus is available as a library that can be easily integrated into your Rust projects. +Here is an example of evaluating a simple Rego policy: + ```rust use anyhow::Result; use regorus::*; @@ -36,12 +39,13 @@ fn main() -> Result<()> { } ``` -Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few builtins. -See [OPA Conformance](#opa-conformance) below. +Regorus passes the [OPA v0.60.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few +builtins. See [OPA Conformance](#opa-conformance) below. ## Getting Started -[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that shows how to integrate Regorus into your project and evaluate Rego policies. +[examples/regorus](https://github.com/microsoft/regorus/blob/main/examples/regorus.rs) is an example program that +shows how to integrate Regorus into your project and evaluate Rego policies. To build and install it, do @@ -94,7 +98,8 @@ This produces the following output } ``` -Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json) (borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)): +Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json) +(borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)): ```bash $ regorus eval -d examples/example.rego -i examples/input.json data.example @@ -178,7 +183,8 @@ The test driver can be invoked by running: $ cargo test -r --test opa ``` -Currently, Regorus passes all the non-builtin specific tests. See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing). +Currently, Regorus passes all the non-builtin specific tests. +See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing). The following test suites don't pass fully due to mising builtins: - `cryptoparsersaprivatekeys` @@ -219,7 +225,8 @@ They are captured in the following [github issues](https://github.com/microsoft/ ### Grammar -The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md) in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats. +The grammar used by Regorus to parse Rego policies is described in [grammar.md](https://github.com/microsoft/regorus/blob/main/docs/grammar.md) +in both [W3C EBNF](https://www.w3.org/Notation.html) and [RailRoad Diagram](https://en.wikipedia.org/wiki/Syntax_diagram) formats. ## Contributing diff --git a/src/engine.rs b/src/engine.rs index 525cb00..e41fbcd 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,12 +8,14 @@ use crate::parser::*; use crate::scheduler::*; use crate::utils::gather_functions; use crate::value::*; +use crate::QueryResults; use std::convert::AsRef; use std::path::Path; use anyhow::Result; +/// The Rego evaluation engine. #[derive(Clone)] pub struct Engine { modules: Vec>, @@ -21,6 +23,7 @@ pub struct Engine { prepared: bool, } +/// Create a default engine. impl Default for Engine { fn default() -> Self { Self::new() diff --git a/src/interpreter.rs b/src/interpreter.rs index 556e935..ace9b6a 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -9,10 +9,10 @@ use crate::parser::Parser; use crate::scheduler::*; use crate::utils::*; use crate::value::*; +use crate::{Expression, Location, QueryResult, QueryResults}; use anyhow::{anyhow, bail, Result}; use log::info; -use serde::Serialize; use std::collections::btree_map::Entry as BTreeMapEntry; use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap}; use std::ops::Bound::*; @@ -73,42 +73,6 @@ impl Default for Interpreter { } } -#[derive(Debug, Clone, Serialize)] -pub struct Location { - pub row: u16, - pub col: u16, -} - -#[derive(Debug, Clone, Serialize)] -pub struct Expression { - pub value: Value, - pub text: Rc, - pub location: Location, -} - -#[derive(Debug, Clone, Serialize)] -pub struct QueryResult { - // Expressions is shown first to match OPA. - pub expressions: Vec, - #[serde(skip_serializing_if = "Value::is_empty_object")] - pub bindings: Value, -} - -impl Default for QueryResult { - fn default() -> Self { - Self { - bindings: Value::new_object(), - expressions: vec![], - } - } -} - -#[derive(Debug, Clone, Default, Serialize)] -pub struct QueryResults { - #[serde(skip_serializing_if = "Vec::is_empty")] - pub result: Vec, -} - #[derive(Debug, Clone)] struct Context { key_expr: Option, diff --git a/src/lib.rs b/src/lib.rs index b6bc2b3..3944e78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ // Use README.md as crate documentation. #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] +use serde::Serialize; + mod ast; mod builtins; mod engine; @@ -16,9 +18,166 @@ mod utils; mod value; pub use engine::Engine; -pub use interpreter::{QueryResult, QueryResults}; pub use value::Value; +/// Location of an [`Expression`] in a Rego query. +/// +/// ``` +/// # use regorus::Engine; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate " \n 1 + 2". +/// let results = Engine::new().eval_query(" \n 1 + 2".to_string(), false)?; +/// +/// // Fetch the location for the expression. +/// let loc = &results.result[0].expressions[0].location; +/// +/// assert_eq!(loc.row, 2); +/// assert_eq!(loc.col, 3); +/// # Ok(()) +/// # } +/// ```` +/// See also [`QueryResult`]. +#[derive(Debug, Clone, Serialize)] +pub struct Location { + /// Line number. Starts at 1. + pub row: u16, + /// Column number. Starts at 1. + pub col: u16, +} + +/// An expression in a Rego query. +/// +/// ``` +/// # use regorus::*; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate "1 + 2". +/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?; +/// +/// // Fetch the expression from results. +/// let expr = &results.result[0].expressions[0]; +/// +/// assert_eq!(expr.value, Value::from(3u64)); +/// assert_eq!(expr.text.as_ref(), "1 + 2"); +/// # Ok(()) +/// # } +/// ``` +/// See also [`QueryResult`]. +#[derive(Debug, Clone, Serialize)] +pub struct Expression { + /// Computed value of the expression. + pub value: Value, + + /// The Rego expression. + pub text: std::rc::Rc, + + /// Location of the expression in the query string. + pub location: Location, +} + +/// Result of evaluating a Rego query. +/// +/// A query containing single expression. +/// ``` +/// # use regorus::*; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate "1 + 2". +/// let results = Engine::new().eval_query("1 + 2".to_string(), false)?; +/// +/// // Fetch the first (sole) result. +/// let result = &results.result[0]; +/// +/// assert_eq!(result.expressions[0].value, Value::from(3u64)); +/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2"); +/// # Ok(()) +/// # } +/// ``` +/// +/// A query containing multiple expressions. +/// ``` +/// # use regorus::*; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate "1 + 2; 3.5 * 4". +/// let results = Engine::new().eval_query("1 + 2; 3.55 * 4".to_string(), false)?; +/// +/// // Fetch the first (sole) result. +/// let result = &results.result[0]; +/// +/// // First expression. +/// assert_eq!(result.expressions[0].value, Value::from(3u64)); +/// assert_eq!(result.expressions[0].text.as_ref(), "1 + 2"); +/// +/// // Second expression. +/// assert_eq!(result.expressions[1].value, Value::from(14.2)); +/// assert_eq!(result.expressions[1].text.as_ref(), "3.55 * 4"); +/// # Ok(()) +/// # } +/// ``` +/// +/// Expressions that create bindings (i.e. associate names to values) evaluate to +/// either true or false. The value of bindings are available in the `bindings` field. +/// ``` +/// # use regorus::*; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate "x = 1; y = x > 0". +/// let results = Engine::new().eval_query("x = 1; y = x > 0".to_string(), false)?; +/// +/// // Fetch the first (sole) result. +/// let result = &results.result[0]; +/// +/// // First expression is true. +/// assert_eq!(result.expressions[0].value, Value::from(true)); +/// assert_eq!(result.expressions[0].text.as_ref(), "x = 1"); +/// +/// // Second expression is true. +/// assert_eq!(result.expressions[1].value, Value::from(true)); +/// assert_eq!(result.expressions[1].text.as_ref(), "y = x > 0"); +/// +/// // bindings contains the value for each named expession. +/// assert_eq!(result.bindings[&Value::from("x")], Value::from(1u64)); +/// assert_eq!(result.bindings[&Value::from("y")], Value::from(true)); +/// # Ok(()) +/// # } +/// ``` +/// +/// If any expression evaluates to false, then no results are produces. +/// ``` +/// # use regorus::*; +/// # fn main() -> anyhow::Result<()> { +/// // Create engine and evaluate "true; true; false". +/// let results = Engine::new().eval_query("true; true; false".to_string(), false)?; +/// +/// assert!(results.result.is_empty()); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug, Clone, Serialize)] +pub struct QueryResult { + /// Expressions in the query. + /// + /// Each statement in the query is treated as a separte expression. + /// + pub expressions: Vec, + + /// Bindings created in the query. + #[serde(skip_serializing_if = "Value::is_empty_object")] + pub bindings: Value, +} + +impl Default for QueryResult { + fn default() -> Self { + Self { + bindings: Value::new_object(), + expressions: vec![], + } + } +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct QueryResults { + #[serde(skip_serializing_if = "Vec::is_empty")] + pub result: Vec, +} + /// Items in `unstable` are likely to change. pub mod unstable { pub use crate::ast::*; diff --git a/src/value.rs b/src/value.rs index 219291b..35f8b44 100644 --- a/src/value.rs +++ b/src/value.rs @@ -238,6 +238,24 @@ impl Value { } } +impl From for Value { + fn from(b: bool) -> Self { + Value::Bool(b) + } +} + +impl From for Value { + fn from(s: String) -> Self { + Value::String(s.into()) + } +} + +impl From<&str> for Value { + fn from(s: &str) -> Self { + Value::String(s.into()) + } +} + impl From for Value { fn from(n: u128) -> Self { Value::Number(Number::from(n))