Compare commits

...

12 Commits

Author SHA1 Message Date
Anand Krishnamoorthi
502b830c19 chore: release (#310)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-09-04 10:22:55 -07:00
Anand Krishnamoorthi
8003cfc5b7 fix: Handle parsing corner cases (#309)
A block with a single or expression needs to be treated as a comprehension instead of a
set/array with 1 item. e.g.: {1 | 1 }, [2 | foo]

Allow successfully parsing object comprehensions as rule body
x if { 1:2 | 1 }

fixes #306, fixes #307

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-09-04 10:02:14 -07:00
Anand Krishnamoorthi
a4a80d7fc6 fix: Propagate errors encountered in argument evaluation (#308)
fixes #301

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-31 13:46:55 -07:00
Anand Krishnamoorthi
af5071446b feat: OPA v0.68.0. Engine::set_rego_v1 (#305)
Provide ability in the engine to treat subsequently loaded policies
as rego.v1.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-30 15:11:09 -07:00
Anand Krishnamoorthi
edd0ccca5b fix: Issues #302, #303 (#304)
Handle undefined values correctly in ordered-else. Previously an undefined value
in one of the blocks could cause the entire rule to evaluate to undefined.

Handle undefined values correctly in generic rule refs to prevent them from
propagating to output.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-29 09:47:45 -07:00
dependabot[bot]
fec6f8f8b4 Bump rexml in /bindings/ruby in the bundler group across 1 directory (#300)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.3 to 3.3.6
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-22 14:13:29 -07:00
Anand Krishnamoorthi
dff65f0329 chore: release (#298)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-16 08:45:52 -07:00
dependabot[bot]
6bf40c7394 Update cbindgen requirement from 0.26.0 to 0.27.0 (#296)
Updates the requirements on [cbindgen](https://github.com/mozilla/cbindgen) to permit the latest version.
- [Release notes](https://github.com/mozilla/cbindgen/releases)
- [Changelog](https://github.com/mozilla/cbindgen/blob/master/CHANGES)
- [Commits](https://github.com/mozilla/cbindgen/compare/0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: cbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 09:09:53 -07:00
Anand Krishnamoorthi
a488a84969 fix: Match OPA behavior for split (#295)
In case of empty delimiter, Rust's split returns leading and trailing
empty strings whereas Golang's doesn't.
Change behavior to match Golang/OPA.

fixes #291

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-08 14:53:01 -07:00
dependabot[bot]
e4a58ad1dc Bump rexml in /bindings/ruby in the bundler group across 1 directory (#294)
Bumps the bundler group with 1 update in the /bindings/ruby directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.2 to 3.3.3
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 13:08:09 -07:00
dependabot[bot]
430a453fde Update csbindgen requirement from =1.9.0 to =1.9.3 (#292)
Updates the requirements on [csbindgen](https://github.com/Cysharp/csbindgen) to permit the latest version.
- [Release notes](https://github.com/Cysharp/csbindgen/releases)
- [Commits](https://github.com/Cysharp/csbindgen/compare/1.9.0...1.9.3)

---
updated-dependencies:
- dependency-name: csbindgen
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 11:39:56 -07:00
Anand Krishnamoorthi
ef549a6528 fix: Merge data to init document (#293)
Init document is the aggregated data documen that the user has
specified using multiple `add_data` calls. Each query evaluation
starts of by initializing the current data to the init document.

Previously `add_data` was incorrectly added to the current document,
causing the added data to be lost if the addition happened after query
evaluation.

With this fix, scenarios where data addition may be interspersed with
query evaluation calls are supported.

Also provide a get_data method to obtain the (init) data document.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2024-08-07 11:39:23 -07:00
19 changed files with 629 additions and 175 deletions

View File

@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.4](https://github.com/microsoft/regorus/compare/regorus-v0.2.3...regorus-v0.2.4) - 2024-09-04
### Added
- OPA v0.68.0. Engine::set_rego_v1 ([#305](https://github.com/microsoft/regorus/pull/305))
### Fixed
- Handle parsing corner cases ([#309](https://github.com/microsoft/regorus/pull/309))
- Propagate errors encountered in argument evaluation ([#308](https://github.com/microsoft/regorus/pull/308))
- Issues [#302](https://github.com/microsoft/regorus/pull/302), [#303](https://github.com/microsoft/regorus/pull/303) ([#304](https://github.com/microsoft/regorus/pull/304))
## [0.2.3](https://github.com/microsoft/regorus/compare/regorus-v0.2.2...regorus-v0.2.3) - 2024-08-16
### Fixed
- Match OPA behavior for split ([#295](https://github.com/microsoft/regorus/pull/295))
- Merge data to init document ([#293](https://github.com/microsoft/regorus/pull/293))
### Other
- Update cbindgen requirement from 0.26.0 to 0.27.0 ([#296](https://github.com/microsoft/regorus/pull/296))
- Bump rexml in /bindings/ruby in the bundler group across 1 directory ([#294](https://github.com/microsoft/regorus/pull/294))
- Update csbindgen requirement from =1.9.0 to =1.9.3 ([#292](https://github.com/microsoft/regorus/pull/292))
## [0.2.2](https://github.com/microsoft/regorus/compare/regorus-v0.2.1...regorus-v0.2.2) - 2024-07-28
### Added

View File

@@ -12,7 +12,7 @@ members = [
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.2.2"
version = "0.2.4"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"

View File

@@ -10,7 +10,7 @@ Regorus is also
- *cross-platform* - Written in platform-agnostic Rust.
- *no_std compatible* - Regorus can be used in `no_std` environments too. Most of the builtins are supported.
- *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
- *compliant* - Regorus is mostly compliant with the latest [OPA release v0.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins.
- *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust.
See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
Support for extensibility using other languages coming soon.
@@ -99,7 +99,7 @@ $ cargo build -r --example regorus --no-default-features; strip target/release/e
-rwxr-xr-x 1 anand staff 1.9M May 11 22:04 target/release/examples/regorus*
```
Regorus passes the [OPA v0.67.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
Regorus passes the [OPA v0.68.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few
builtins. See [OPA Conformance](#opa-conformance) below.
## Bindings
@@ -276,7 +276,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
```
## OPA Conformance
Regorus has been verified to be compliant with [OPA v0.67.0](https://github.com/open-policy-agent/opa/releases/tag/v0.67.0)
Regorus has been verified to be compliant with [OPA v0.68.0](https://github.com/open-policy-agent/opa/releases/tag/v0.68.0)
using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced.
The test driver can be invoked by running:

View File

@@ -20,5 +20,5 @@ coverage = ["regorus/coverage"]
custom_allocator = []
[build-dependencies]
cbindgen = "0.26.0"
csbindgen = "=1.9.0"
cbindgen = "0.27.0"
csbindgen = "=1.9.3"

View File

@@ -23,7 +23,7 @@ GEM
rake-compiler-dock (1.5.1)
rb_sys (0.9.99)
regexp_parser (2.9.2)
rexml (3.3.2)
rexml (3.3.6)
strscan
rubocop (1.65.0)
json (~> 2.3)

View File

@@ -33,6 +33,7 @@ fn add_policy_from_file(engine: &mut regorus::Engine, path: String) -> Result<St
engine.add_policy(path.clone(), read_file(&path)?)
}
#[allow(clippy::too_many_arguments)]
fn rego_eval(
bundles: &[String],
files: &[String],
@@ -41,6 +42,7 @@ fn rego_eval(
enable_tracing: bool,
non_strict: bool,
#[cfg(feature = "coverage")] coverage: bool,
v1: bool,
) -> Result<()> {
// Create engine.
let mut engine = regorus::Engine::new();
@@ -50,6 +52,8 @@ fn rego_eval(
#[cfg(feature = "coverage")]
engine.set_enable_coverage(coverage);
engine.set_rego_v1(v1);
// Load files from given bundles.
for dir in bundles.iter() {
let entries =
@@ -233,6 +237,10 @@ enum RegorusCommand {
#[cfg(feature = "coverage")]
#[arg(long, short)]
coverage: bool,
/// Turn on rego.v1
#[arg(long)]
v1: bool,
},
/// Tokenize a Rego policy.
@@ -274,6 +282,7 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
} => rego_eval(
&bundles,
&data,
@@ -283,6 +292,7 @@ fn main() -> Result<()> {
non_strict,
#[cfg(feature = "coverage")]
coverage,
v1,
),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file),

View File

@@ -146,11 +146,18 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
let s = ensure_string(name, &params[0], &args[0])?;
let delimiter = ensure_string(name, &params[1], &args[1])?;
Ok(Value::from_array(
// Handle https://github.com/microsoft/regorus/issues/291
let parts: Vec<Value> = if delimiter.as_ref() == "" {
// If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
// Therefore avoid str::split and instead return each char as a Value::String.
s.chars().map(|c| Value::from(c.to_string())).collect()
} else {
s.split(delimiter.as_ref())
.map(|s| Value::String(s.into()))
.collect(),
))
.collect()
};
Ok(Value::from(parts))
}
fn to_string(v: &Value, unescape: bool) -> String {

View File

@@ -20,6 +20,7 @@ pub struct Engine {
modules: Vec<Ref<Module>>,
interpreter: Interpreter,
prepared: bool,
rego_v1: bool,
}
/// Create a default engine.
@@ -36,9 +37,35 @@ impl Engine {
modules: vec![],
interpreter: Interpreter::new(),
prepared: false,
rego_v1: false,
}
}
/// Turn rego.v1 on/off for subsequently added policies.
///
/// Explicit import rego.v1 is not needed if set.
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.set_rego_v1(true);
/// engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow if true # if keyword is automatically imported
/// "#.to_string())?;
///
/// # Ok(())
/// # }
/// ```
///
pub fn set_rego_v1(&mut self, rego_v1: bool) {
self.rego_v1 = rego_v1;
}
/// Add a policy.
///
/// The policy file will be parsed and converted to AST representation.
@@ -67,7 +94,7 @@ impl Engine {
///
pub fn add_policy(&mut self, path: String, rego: String) -> Result<String> {
let source = Source::from_contents(path, rego)?;
let mut parser = Parser::new(&source)?;
let mut parser = self.make_parser(&source)?;
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
@@ -98,7 +125,7 @@ impl Engine {
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn add_policy_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<String> {
let source = Source::from_file(path)?;
let mut parser = Parser::new(&source)?;
let mut parser = self.make_parser(&source)?;
let module = Ref::new(parser.parse()?);
self.modules.push(module.clone());
// if policies change, interpreter needs to be prepared again
@@ -239,7 +266,7 @@ impl Engine {
/// # }
/// ```
pub fn clear_data(&mut self) {
self.interpreter.set_data(Value::new_object());
self.interpreter.set_init_data(Value::new_object());
self.prepared = false;
}
@@ -276,7 +303,40 @@ impl Engine {
bail!("data must be object");
}
self.prepared = false;
self.interpreter.get_data_mut().merge(data)
self.interpreter.get_init_data_mut().merge(data)
}
/// Get the data document.
///
/// The returned value is the data document that has been constructed using
/// one or more calls to [`Engine::add_data`]. The values of policy rules are
/// not included in the returned document.
///
///
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// // If not set, data document is empty.
/// assert_eq!(engine.get_data(), Value::new_object());
///
/// // Merge { "x" : 1, "y" : {} }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?).is_ok());
///
/// // Merge { "z" : 2 }
/// assert!(engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?).is_ok());
///
/// let data = engine.get_data();
/// assert_eq!(data["x"], Value::from(1));
/// assert_eq!(data["y"], Value::new_object());
/// assert_eq!(data["z"], Value::from(2));
///
/// # Ok(())
/// # }
/// ```
pub fn get_data(&self) -> Value {
self.interpreter.get_init_data().clone()
}
pub fn add_data_json(&mut self, data_json: &str) -> Result<()> {
@@ -395,7 +455,7 @@ impl Engine {
// Parse the query.
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let mut parser = self.make_parser(&query_source)?;
let query_node = parser.parse_user_query()?;
if query_node.span.text() == "data" {
self.eval_modules(enable_tracing)?;
@@ -512,7 +572,7 @@ impl Engine {
// Parse the query.
let query_source = Source::from_contents("<query.rego>".to_string(), query)?;
let mut parser = Parser::new(&query_source)?;
let mut parser = self.make_parser(&query_source)?;
let query_node = parser.parse_user_query()?;
let query_schedule = Analyzer::new().analyze_query_snippet(&self.modules, &query_node)?;
self.interpreter.eval_user_query(
@@ -537,11 +597,7 @@ impl Engine {
self.interpreter.set_modules(&self.modules);
self.interpreter.clear_builtins_cache();
// when the interpreter is prepared the initial data is saved
// the data will be reset to init_data each time clean_internal_evaluation_state is called
let init_data = self.interpreter.get_data_mut().clone();
self.interpreter.set_init_data(init_data);
// clean_internal_evaluation_state will set data to an efficient clont of use supplied init_data
// Initialize the with-document with initial data values.
// with-modifiers will be applied to this document.
self.interpreter.init_with_document()?;
@@ -841,4 +897,12 @@ impl Engine {
serde_json::to_string_pretty(&ast).map_err(anyhow::Error::msg)
}
fn make_parser<'a>(&self, source: &'a Source) -> Result<Parser<'a>> {
let mut parser = Parser::new(source)?;
if self.rego_v1 {
parser.enable_rego_v1()?;
}
Ok(parser)
}
}

View File

@@ -216,18 +216,22 @@ impl Interpreter {
self.modules = modules.to_vec();
}
pub fn set_init_data(&mut self, init_data: Value) {
self.init_data = init_data;
}
pub fn set_data(&mut self, data: Value) {
self.data = data;
}
pub fn get_data_mut(&mut self) -> &mut Value {
&mut self.data
}
pub fn set_init_data(&mut self, data: Value) {
self.init_data = data;
}
pub fn get_init_data(&self) -> &Value {
&self.init_data
}
pub fn get_init_data_mut(&mut self) -> &mut Value {
&mut self.init_data
}
pub fn set_traces(&mut self, enable_tracing: bool) {
self.traces = match enable_tracing {
true => Some(vec![]),
@@ -1554,6 +1558,10 @@ impl Interpreter {
let mut obj = &mut self.data;
let len = path.len();
for (idx, p) in path.into_iter().enumerate() {
// Stop at the first undefined component in the path
if p == Value::Undefined {
break;
}
if idx == len - 1 {
// last key.
if is_set {
@@ -1688,6 +1696,7 @@ impl Interpreter {
}
if output == Value::Undefined || !comps_defined {
ctx.rule_value = Value::Undefined;
return Ok(false);
}
@@ -1867,14 +1876,14 @@ impl Interpreter {
self.hoist_loops_impl(oe, &mut loops);
}
self.eval_output_expr_in_loop(&loops[..])?;
let r = self.eval_output_expr_in_loop(&loops[..])?;
let ctx = self.get_current_context()?;
if let Some(_oe) = &ctx.output_expr {
// Ensure that at least one output was generated.
Ok(ctx.value != Value::Undefined)
Ok(ctx.rule_value != Value::Undefined)
} else {
Ok(true)
Ok(r)
}
}
@@ -2274,15 +2283,8 @@ impl Interpreter {
};
let mut param_values = Vec::with_capacity(params.len());
let mut error = None;
for p in params {
match self.eval_expr(p) {
Ok(v) => param_values.push(v),
Err(e) => {
error = Some(Err(e));
break;
}
}
param_values.push(self.eval_expr(p)?);
}
let orig_fcn_path = fcn_path;
@@ -2299,9 +2301,6 @@ impl Interpreter {
if param_values.iter().any(|v| v == &Value::Undefined) {
return Ok(Value::Undefined);
}
if let Some(err) = error {
err?;
};
return Ok(v.clone());
}
_ => orig_fcn_path.clone(),
@@ -2944,7 +2943,6 @@ impl Interpreter {
});
}
result = self.eval_query(&body.query);
if matches!(&result, Ok(true) | Err(_)) {
break;
}

View File

@@ -40,6 +40,18 @@ impl<'source> Parser<'source> {
})
}
pub fn enable_rego_v1(&mut self) -> Result<()> {
self.turn_on_rego_v1(self.tok.1.clone())
}
fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
}
Ok(())
}
pub fn token_text(&self) -> &str {
match self.tok.0 {
TokenKind::Symbol | TokenKind::Number | TokenKind::Ident | TokenKind::Eof => {
@@ -1026,6 +1038,23 @@ impl<'source> Parser<'source> {
let mut literals = vec![];
let stmt = match self.parse_literal_stmt() {
Ok(_) if self.token_text() == ":" => {
// This is likely an object comprehension.
// Restore the state and return.
*self = state;
bail!("try parsing as comprehension");
}
Ok(stmt) if self.token_text() == end_delim => {
// Treat { 1 | 1 } as a comprehension instead of a
// set of 1 element.
if let Literal::Expr { expr: e, .. } = &stmt.literal {
if matches!(e.as_ref(), Expr::BinExpr { op: BinOp::Or, .. }) {
*self = state;
bail!("try parse as comprehension");
}
}
stmt
}
Ok(stmt) => stmt,
Err(e) if is_definite_query => return Err(e),
Err(e) if matches!(self.token_text(), "=" | ":=") => return Err(e),
@@ -1648,10 +1677,7 @@ impl<'source> Parser<'source> {
let is_future_kw =
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
}
self.turn_on_rego_v1(span.clone())?;
true
} else {
self.handle_import_future_keywords(&comps)?

View File

@@ -422,3 +422,32 @@ fn one_yaml() -> Result<()> {
fn run(path: &str) {
yaml_test(path).unwrap()
}
#[test]
fn test_get_data() -> Result<()> {
let mut engine = Engine::new();
// Merge { "x" : 1, "y" : {} }
engine.add_data(Value::from_json_str(r#"{ "x" : 1, "y" : {}}"#)?)?;
// Merge { "z" : 2 }
engine.add_data(Value::from_json_str(r#"{ "z" : 2 }"#)?)?;
// Add a policy
engine.add_policy("policy.rego".to_string(), "package a".to_string())?;
// Evaluate virtual data document. The virtual document includes all rules as well.
let v_data = engine.eval_query("data".to_string(), false)?.result[0].expressions[0]
.value
.clone();
// There must be an empty package.
assert_eq!(v_data["a"], Value::new_object());
// Get the data document.
let data = engine.get_data();
// There must NOT be any value of `a`.
assert_eq!(data["a"], Value::Undefined);
Ok(())
}

View File

@@ -0,0 +1,17 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: empty separator
data: {}
modules: []
query: "x := split(\"test\", \"\")"
want_result:
x: ["t", "e", "s", "t"]
- note: empty separator, empty string
data: {}
modules: []
query: "x := split(\"\", \"\")"
want_result:
x: []

View File

@@ -59,3 +59,20 @@ cases:
a1 = inc(5)
query: data.test
want_result: {}
- note: call parameter raises error
data: {}
modules:
- |
package test
import rego.v1
bar := 1 if {
1 + "hello"
}
foo := 1 if {
count(bar)
}
query: data.test
error: expects numeric argument.

View File

@@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: treat { 1 | 1 } as comprehension
data: {}
modules:
- |
package test
import rego.v1
x if { 1 | 1 }
y := { 1 | 1 }
z := { (1) | 1}
# Parsed as a set
a := { ({1} | {2}) }
b := v if { { 1 | 1 } = v }
query: data.test
want_result:
x: true
y:
set!: [1]
z:
set!: [1]
a:
set!:
- set!: [1, 2]
b:
set!: [1]
- note: rule body is object comprehension
data: {}
modules:
- |
package test
import future.keywords
x if { 1:2 | some p in [1,2] }
y := 2 if { 1:2 | some p in [1,2] }
query: data.test
want_result:
x: true
y: 2

View File

@@ -2,17 +2,34 @@
# Licensed under the MIT License.
cases:
- note: else without body
# - note: else without body
# data: {}
# modules:
# - |
# package test
# x = 4 {
# false
# } else = 5
# y = 6
# query: data.test
# want_result:
# x: 5
# y: 6
- note: undefined values being assigned
data: {}
modules:
- |
package test
x = 4 {
false
} else = 5
y = 6
import rego.v1
x := data.y if {
true
} else := 2 if {
true
}
query: data.test
want_result:
x: 5
y: 6
x: 2

View File

@@ -0,0 +1,26 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: undefined components
data: {}
modules:
- |
package test
import rego.v1
principal := input.principal
action := input.action
p[principal][action] := 1 if {
some a in []
}
q[principal][action] contains 1 if {
some a in []
}
query: data.test
want_result:
p: {}
q: {}

View File

@@ -172,6 +172,64 @@ struct Cli {
generate: bool,
}
fn stateful_policy_test() -> Result<()> {
// Create an engine for evaluating Rego policies.
let mut engine = regorus::Engine::new();
let policy = String::from(
r#"
package example
import rego.v1
default allow := false
allow if {
print("data.allowed_actions = ", data.allowed_actions)
input.action in data.allowed_actions["user1"]
print("This rule should be allowed")
}
"#,
);
// Add policy to the engine.
engine.add_policy(String::from("policy.rego"), policy)?;
// Evaluate first input. Expect to evaluate to false, since state is not set
engine.set_input(regorus::Value::from_json_str(
r#"{
"action": "write"
}"#,
)?);
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
println!("Received result: {:?}", r);
assert_eq!(r, false);
// Add data to engine. Set state
engine.add_data(regorus::Value::from_json_str(
r#"{
"allowed_actions": {
"user1" : ["read", "write"]
}}"#,
)?)?;
// Evaluate second input. Expect to evaluate to true, since state has been set now
engine.set_input(regorus::Value::from_json_str(
r#"{
"action": "write"
}"#,
)?);
let r = engine.eval_bool_query(String::from("data.example.allow"), false)?;
println!("Received result: {:?}", r);
assert_eq!(
r, true,
"expect result to be true since rule evaluates to true after state has been updated, per rego logs"
);
Ok(())
}
fn main() -> Result<()> {
let cli = Cli::parse();
run_kata_tests(
@@ -179,5 +237,6 @@ fn main() -> Result<()> {
&cli.name,
cli.coverage,
cli.generate,
)
)?;
stateful_policy_test()
}

View File

@@ -1,115 +1,230 @@
aggregates
all
any
arithmetic
array
assignments
base64builtins
base64urlbuiltins
baseandvirtualdocs
bitsand
bitsnegate
bitsor
bitsshiftleft
bitsshiftright
bitsxor
casts
comparisonexpr
completedoc
compositebasedereference
compositereferences
comprehensions
containskeyword
cryptohmacequal
cryptohmacmd5
cryptohmacsha1
cryptohmacsha256
cryptohmacsha512
cryptomd5
cryptosha1
cryptosha256
dataderef
defaultkeyword
disjunction
elsekeyword
embeddedvirtualdoc
eqexpr
evaltermexpr
every
example
fix1863
functionerrors
functions
globmatch
globquotemeta
helloworld
hexbuiltins
indexing
indirectreferences
inputvalues
intersection
jsonbuiltins
jsonfilter
jsonfilteridempotent
jsonremove
jsonremoveidempotent
jsonschema
jwtbuiltins
negation
nestedreferences
numbersrange
numbersrangestep
objectfilter
objectfilteridempotent
objectfilternonstringkey
objectget
objectkeys
objectremove
objectremoveidempotent
objectremovenonstringkey
objectunion
objectunionn
partialdocconstants
partialiter
partialobjectdoc
partialsetdoc
planner-ir
rand
reachable
refheads
regexfind
regexfindallstringsubmatch
regexisvalid
regexmatch
regexmatchtemplate
regexreplace
regexsplit
replacen
semvercompare
semverisvalid
sets
sprintf
strings
subset
toarray
topdowndynamicdispatch
toset
time
trim
trimleft
trimprefix
trimright
trimspace
trimsuffix
type
typebuiltin
typenamebuiltin
undos
union
units
urlbuiltins
uuid
varreferences
virtualdocs
walkbuiltin
withkeyword
v0/aggregates
v0/all
v0/any
v0/arithmetic
v0/array
v0/assignments
v0/base64builtins
v0/base64urlbuiltins
v0/baseandvirtualdocs
v0/bitsand
v0/bitsnegate
v0/bitsor
v0/bitsshiftleft
v0/bitsshiftright
v0/bitsxor
v0/casts
v0/comparisonexpr
v0/completedoc
v0/compositebasedereference
v0/compositereferences
v0/comprehensions
v0/containskeyword
v0/cryptohmacequal
v0/cryptohmacmd5
v0/cryptohmacsha1
v0/cryptohmacsha256
v0/cryptohmacsha512
v0/cryptomd5
v0/cryptosha1
v0/cryptosha256
v0/dataderef
v0/defaultkeyword
v0/disjunction
v0/elsekeyword
v0/embeddedvirtualdoc
v0/eqexpr
v0/evaltermexpr
v0/every
v0/example
v0/fix1863
v0/functionerrors
v0/functions
v0/globmatch
v0/globquotemeta
v0/helloworld
v0/hexbuiltins
v0/indexing
v0/indirectreferences
v0/inputvalues
v0/intersection
v0/jsonbuiltins
v0/jsonfilter
v0/jsonfilteridempotent
v0/jsonremove
v0/jsonremoveidempotent
v0/jsonschema
v0/jwtbuiltins
v0/negation
v0/nestedreferences
v0/numbersrange
v0/numbersrangestep
v0/objectfilter
v0/objectfilteridempotent
v0/objectfilternonstringkey
v0/objectget
v0/objectkeys
v0/objectremove
v0/objectremoveidempotent
v0/objectremovenonstringkey
v0/objectunion
v0/objectunionn
v0/partialdocconstants
v0/partialiter
v0/partialobjectdoc
v0/partialsetdoc
v0/planner-ir
v0/rand
v0/reachable
v0/refheads
v0/regexfind
v0/regexfindallstringsubmatch
v0/regexisvalid
v0/regexmatch
v0/regexmatchtemplate
v0/regexreplace
v0/regexsplit
v0/replacen
v0/semvercompare
v0/semverisvalid
v0/sets
v0/sprintf
v0/strings
v0/subset
v0/toarray
v0/topdowndynamicdispatch
v0/toset
v0/time
v0/trim
v0/trimleft
v0/trimprefix
v0/trimright
v0/trimspace
v0/trimsuffix
v0/type
v0/typebuiltin
v0/typenamebuiltin
v0/undos
v0/union
v0/units
v0/urlbuiltins
v0/uuid
v0/varreferences
v0/virtualdocs
v0/walkbuiltin
v0/withkeyword
v1/aggregates
v1/all
v1/any
v1/arithmetic
v1/array
v1/assignments
v1/base64builtins
v1/base64urlbuiltins
v1/baseandvirtualdocs
v1/bitsand
v1/bitsnegate
v1/bitsor
v1/bitsshiftleft
v1/bitsshiftright
v1/bitsxor
v1/casts
v1/comparisonexpr
v1/completedoc
v1/compositebasedereference
v1/compositereferences
v1/comprehensions
v1/containskeyword
v1/cryptohmacequal
v1/cryptohmacmd5
v1/cryptohmacsha1
v1/cryptohmacsha256
v1/cryptohmacsha512
v1/cryptomd5
v1/cryptosha1
v1/cryptosha256
v1/dataderef
v1/defaultkeyword
v1/disjunction
v1/elsekeyword
v1/embeddedvirtualdoc
v1/eqexpr
v1/evaltermexpr
v1/every
v1/example
v1/fix1863
v1/functionerrors
v1/functions
v1/globmatch
v1/globquotemeta
v1/helloworld
v1/hexbuiltins
v1/indexing
v1/indirectreferences
v1/inputvalues
v1/intersection
v1/jsonbuiltins
v1/jsonfilter
v1/jsonfilteridempotent
v1/jsonremove
v1/jsonremoveidempotent
v1/jsonschema
v1/jwtbuiltins
v1/negation
v1/nestedreferences
v1/numbersrange
v1/numbersrangestep
v1/objectfilter
v1/objectfilteridempotent
v1/objectfilternonstringkey
v1/objectget
v1/objectkeys
v1/objectremove
v1/objectremoveidempotent
v1/objectremovenonstringkey
v1/objectunion
v1/objectunionn
v1/partialdocconstants
v1/partialiter
v1/partialobjectdoc
v1/partialsetdoc
v1/planner-ir
v1/rand
v1/reachable
v1/refheads
v1/regexfind
v1/regexfindallstringsubmatch
v1/regexisvalid
v1/regexmatch
v1/regexmatchtemplate
v1/regexreplace
v1/regexsplit
v1/replacen
v1/semvercompare
v1/semverisvalid
v1/sets
v1/sprintf
v1/strings
v1/subset
v1/toarray
v1/topdowndynamicdispatch
v1/toset
v1/time
v1/trim
v1/trimleft
v1/trimprefix
v1/trimright
v1/trimspace
v1/trimsuffix
v1/type
v1/typebuiltin
v1/typenamebuiltin
v1/undos
v1/union
v1/units
v1/urlbuiltins
v1/uuid
v1/varreferences
v1/virtualdocs
v1/walkbuiltin
v1/withkeyword

View File

@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
const OPA_BRANCH: &str = "v0.67.0";
const OPA_BRANCH: &str = "v0.68.0";
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
@@ -51,12 +51,14 @@ struct YamlTest {
cases: Vec<TestCase>,
}
fn eval_test_case(case: &TestCase) -> Result<Value> {
fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result<Value> {
let mut engine = Engine::new();
#[cfg(feature = "coverage")]
engine.set_enable_coverage(true);
engine.set_rego_v1(is_rego_v1_test);
if let Some(data) = &case.data {
engine.add_data(data.clone())?;
}
@@ -172,6 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
continue;
}
let is_rego_v1_test = path_dir_str.starts_with("v1/");
let entry = status.entry(path_dir_str).or_insert((0, 0, 0));
let yaml_str = std::fs::read_to_string(&path_str)?;
@@ -216,7 +219,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
print!("{:4}: {:90}", entry.2, case.note);
entry.2 += 1;
match (eval_test_case(&case), &case.want_result) {
match (eval_test_case(&case, is_rego_v1_test), &case.want_result) {
(Ok(actual), Some(expected))
if is_json_schema_test && json_schema_tests_check(&actual, &expected) =>
{