- Document Location, Expression, QueryResult (#109)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2024-01-15 17:21:49 -08:00
committed by GitHub
parent 9e1e22432d
commit d39200a52c
5 changed files with 196 additions and 45 deletions

View File

@@ -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

View File

@@ -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<Ref<Module>>,
@@ -21,6 +23,7 @@ pub struct Engine {
prepared: bool,
}
/// Create a default engine.
impl Default for Engine {
fn default() -> Self {
Self::new()

View File

@@ -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<str>,
pub location: Location,
}
#[derive(Debug, Clone, Serialize)]
pub struct QueryResult {
// Expressions is shown first to match OPA.
pub expressions: Vec<Expression>,
#[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<QueryResult>,
}
#[derive(Debug, Clone)]
struct Context {
key_expr: Option<ExprRef>,

View File

@@ -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<str>,
/// 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<Expression>,
/// 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<QueryResult>,
}
/// Items in `unstable` are likely to change.
pub mod unstable {
pub use crate::ast::*;

View File

@@ -238,6 +238,24 @@ impl Value {
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
impl From<String> 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<u128> for Value {
fn from(n: u128) -> Self {
Value::Number(Number::from(n))