Compare commits

...

6 Commits

Author SHA1 Message Date
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
10 changed files with 179 additions and 23 deletions

View File

@@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [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.3"
edition = "2021"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"

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.3)
strscan
rubocop (1.65.0)
json (~> 2.3)

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

@@ -239,7 +239,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 +276,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<()> {
@@ -537,11 +570,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()?;

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![]),

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

@@ -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()
}