From c963e477a389f1f34c08113ce9cda24bb1ff65b9 Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Mon, 10 Mar 2025 11:56:01 -0700 Subject: [PATCH] feat: Update to OPA v1.2.0 (#373) Regorus now defaults to rego v1. `import rego.v1` is no longer needed. Additionally, `future` keywords are automatically imported. See https://www.openpolicyagent.org/docs/latest/v0-upgrade/#changes-to-rego-in-opa-v10 to understand the differences between rego v1 and v0. BREAKING CHANGE: v0 style policies will error out by default. To enable v0 behavior, call engine.set_rego_v0(true) before loading policies. Signed-off-by: Anand Krishnamoorthi --- README.md | 9 ++--- bindings/c-nostd/main.c | 7 +++- bindings/c/main.c | 5 +++ bindings/cpp/main.cpp | 2 + bindings/cpp/regorus.hpp | 3 ++ bindings/csharp/net40/Regorus.cs | 7 ++++ bindings/csharp/net8.0/Program.cs | 1 + bindings/csharp/net8.0/Regorus.cs | 8 ++++ bindings/ffi/src/lib.rs | 40 ++++++++++++++----- bindings/go/main.go | 5 ++- bindings/go/pkg/regorus/mod.go | 11 +++++ bindings/java/Cargo.toml | 2 +- bindings/java/Test.java | 40 +++++++++++-------- bindings/java/src/lib.rs | 14 +++++++ .../java/com/microsoft/regorus/Engine.java | 15 +++++-- bindings/python/Cargo.toml | 2 +- bindings/python/src/lib.rs | 9 +++++ bindings/python/test.py | 2 + bindings/ruby/Gemfile.lock | 2 +- bindings/ruby/README.md | 3 ++ bindings/ruby/ext/regorusrb/Cargo.toml | 2 +- bindings/ruby/ext/regorusrb/src/lib.rs | 8 ++++ bindings/ruby/lib/regorus/version.rb | 2 +- bindings/ruby/test/test_regorus.rb | 8 ++-- bindings/wasm/Cargo.toml | 2 +- bindings/wasm/src/lib.rs | 9 +++++ examples/regorus.rs | 12 +++--- examples/server/allowed_server.rego | 8 ++-- src/builtins/units.rs | 8 ++-- src/engine.rs | 29 +++++++++----- src/parser.rs | 20 +++++----- src/tests/interpreter/mod.rs | 1 + tests/aci/main.rs | 2 + tests/coverage/mod.rs | 1 + tests/kata/main.rs | 20 +++++++--- tests/opa.rs | 16 +++++--- 36 files changed, 244 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index ecf08f5..536fcb4 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ 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.70.0](https://github.com/open-policy-agent/opa/releases/tag/v0.70.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. + - *current* - We strive to keep Regorus up to date with latest OPA release. Regorus defaults to `v1` of the Rego language. + - *compliant* - Regorus is mostly compliant with the latest [OPA release v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.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. @@ -32,7 +32,6 @@ fn main() -> anyhow::Result<()> { let policy = String::from( r#" package example - import rego.v1 allow if { ## All actions are allowed for admins. @@ -99,7 +98,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.70.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few +Regorus passes the [OPA v1.2.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 +275,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.70.0](https://github.com/open-policy-agent/opa/releases/tag/v0.70.0) +Regorus has been verified to be compliant with [OPA v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.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: diff --git a/bindings/c-nostd/main.c b/bindings/c-nostd/main.c index 7d80c4a..99617c8 100644 --- a/bindings/c-nostd/main.c +++ b/bindings/c-nostd/main.c @@ -27,7 +27,7 @@ char* file_to_string(const char* file) { // If regorus is built with custom-allocator, then provide implementation. uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) { - return aligned_alloc(alignment, size); + return (uint8_t*) aligned_alloc(alignment, size); } void regorus_free(uint8_t* ptr) { @@ -41,6 +41,11 @@ int main() { RegorusResult r; char* buffer = NULL; + // Turn on rego v0 since policy uses v0. + r = regorus_engine_set_rego_v0(engine, true); + if (r.status != RegorusStatusOk) + goto error; + // Load policies. r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego"))); free(buffer); diff --git a/bindings/c/main.c b/bindings/c/main.c index 0066b39..3048094 100644 --- a/bindings/c/main.c +++ b/bindings/c/main.c @@ -6,6 +6,11 @@ int main() { RegorusEngine* engine = regorus_engine_new(); RegorusResult r; + // Turn on rego v0 since policy uses v0. + r = regorus_engine_set_rego_v0(engine, true); + if (r.status != RegorusStatusOk) + goto error; + // Load policies. r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego"); if (r.status != RegorusStatusOk) diff --git a/bindings/cpp/main.cpp b/bindings/cpp/main.cpp index 8267247..2596d1c 100644 --- a/bindings/cpp/main.cpp +++ b/bindings/cpp/main.cpp @@ -6,6 +6,7 @@ void example() // Create engine regorus::Engine engine; + engine.set_rego_v0(true); engine.set_enable_coverage(true); // Add policies. @@ -83,6 +84,7 @@ int main() { // Create engine. regorus::Engine engine; + engine.set_rego_v0(true); // Load policies. diff --git a/bindings/cpp/regorus.hpp b/bindings/cpp/regorus.hpp index 36edd82..3cfd128 100644 --- a/bindings/cpp/regorus.hpp +++ b/bindings/cpp/regorus.hpp @@ -54,6 +54,9 @@ namespace regorus { return std::unique_ptr(new Engine(regorus_engine_clone(engine))); } + Result set_rego_v0(bool enable) { + return Result(regorus_engine_set_rego_v0(engine, enable)); + } Result add_policy(const char* path, const char* policy) { return Result(regorus_engine_add_policy(engine, path, policy)); diff --git a/bindings/csharp/net40/Regorus.cs b/bindings/csharp/net40/Regorus.cs index 041126d..c995e63 100644 --- a/bindings/csharp/net40/Regorus.cs +++ b/bindings/csharp/net40/Regorus.cs @@ -62,6 +62,13 @@ namespace Microsoft.WindowsAzure.Regorus.IaaS { return Encoding.UTF8.GetBytes(s + char.MinValue); } + public void SetRegoV0(bool enable) + { + unsafe + { + CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable)); + } + } public void AddPolicy(string path, string rego) { diff --git a/bindings/csharp/net8.0/Program.cs b/bindings/csharp/net8.0/Program.cs index ab52979..fff52a9 100644 --- a/bindings/csharp/net8.0/Program.cs +++ b/bindings/csharp/net8.0/Program.cs @@ -23,6 +23,7 @@ var w = new Stopwatch(); w.Restart(); var engine = new Regorus.Engine(); +engine.SetRegoV0(true); w.Stop(); var newEngineTicks = w.ElapsedTicks; diff --git a/bindings/csharp/net8.0/Regorus.cs b/bindings/csharp/net8.0/Regorus.cs index 72ce2c2..51aa0da 100644 --- a/bindings/csharp/net8.0/Regorus.cs +++ b/bindings/csharp/net8.0/Regorus.cs @@ -51,6 +51,14 @@ namespace Regorus } } + public void SetRegoV0(bool enable) + { + unsafe + { + CheckAndDropResult(RegorusFFI.API.regorus_engine_set_rego_v0(E, enable)); + } + } + public string AddPolicyFromFile(string path) { var pathBytes = NullTerminatedUTF8Bytes(path); diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs index 011c5d6..b9881d7 100644 --- a/bindings/ffi/src/lib.rs +++ b/bindings/ffi/src/lib.rs @@ -222,7 +222,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file( /// Clear policy data. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_data #[no_mangle] pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult { to_regorus_result(|| -> Result<()> { @@ -233,7 +233,7 @@ pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> Regor /// Set input. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_input /// * `input`: JSON encoded value to be used as input to query. #[no_mangle] pub extern "C" fn regorus_engine_set_input_json( @@ -264,7 +264,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file( /// Evaluate query. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_query /// * `query`: Rego expression to be evaluate. #[no_mangle] pub extern "C" fn regorus_engine_eval_query( @@ -289,7 +289,7 @@ pub extern "C" fn regorus_engine_eval_query( /// Evaluate specified rule. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_rule +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule /// * `rule`: Path to the rule. #[no_mangle] pub extern "C" fn regorus_engine_eval_rule( @@ -314,7 +314,7 @@ pub extern "C" fn regorus_engine_eval_rule( /// Enable/disable coverage. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_enable_coverage +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage /// * `enable`: Whether to enable or disable coverage. #[no_mangle] #[cfg(feature = "coverage")] @@ -330,7 +330,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage( /// Get coverage report. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.get_coverage_report +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report #[no_mangle] #[cfg(feature = "coverage")] pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult { @@ -375,7 +375,7 @@ pub extern "C" fn regorus_engine_get_coverage_report_pretty( /// Clear coverage data. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_coverage_data +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data #[no_mangle] #[cfg(feature = "coverage")] pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult { @@ -387,7 +387,7 @@ pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) /// Whether to gather output of print statements. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_gather_prints +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints /// * `enable`: Whether to enable or disable gathering print statements. #[no_mangle] pub extern "C" fn regorus_engine_set_gather_prints( @@ -402,7 +402,7 @@ pub extern "C" fn regorus_engine_set_gather_prints( /// Take all the gathered print statements. /// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.take_prints +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints #[no_mangle] pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult { let output = || -> Result { @@ -437,6 +437,28 @@ pub extern "C" fn regorus_engine_get_ast_as_json(engine: *mut RegorusEngine) -> } } +/// Enable/disable rego v1. +/// +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_rego_v0 +#[no_mangle] +pub extern "C" fn regorus_engine_set_rego_v0( + engine: *mut RegorusEngine, + enable: bool, +) -> RegorusResult { + let output = || -> Result<()> { + to_ref(&engine)?.engine.set_rego_v0(enable); + Ok(()) + }(); + match output { + Ok(()) => RegorusResult { + status: RegorusStatus::RegorusStatusOk, + output: std::ptr::null_mut(), + error_message: std::ptr::null_mut(), + }, + Err(e) => to_regorus_result(Err(e)), + } +} + #[cfg(feature = "custom_allocator")] extern "C" { fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8; diff --git a/bindings/go/main.go b/bindings/go/main.go index a7efed1..807a95b 100644 --- a/bindings/go/main.go +++ b/bindings/go/main.go @@ -16,8 +16,11 @@ func main() { // Create new engine engine := regorus.NewEngine() defer engine.Close() - elapsed1 := time.Since(t) + engine.SetRegoV0(true) + elapsed1 := time.Since(t) + + t = time.Now() // Add policies and data. policies := []string{ diff --git a/bindings/go/pkg/regorus/mod.go b/bindings/go/pkg/regorus/mod.go index 725f7b1..80a68da 100644 --- a/bindings/go/pkg/regorus/mod.go +++ b/bindings/go/pkg/regorus/mod.go @@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine { return c } +func (e *Engine) SetRegoV0(enable bool) (error) { + result := C.regorus_engine_set_rego_v0(e.e, C.bool(enable)) + defer C.regorus_result_drop(result) + + if result.status != C.RegorusStatusOk { + return fmt.Errorf("%s", C.GoString(result.error_message)) + } + + return nil +} + func (e *Engine) AddPolicy(path string, rego string) (string, error) { path_c := C.CString(path) defer C.free(unsafe.Pointer(path_c)) diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 4975e66..6be88bf 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "regorus-java" -version = "0.2.2" +version = "0.3.0" edition = "2021" repository = "https://github.com/microsoft/regorus/bindings/java" description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" diff --git a/bindings/java/Test.java b/bindings/java/Test.java index 4f75f15..ae06f6f 100644 --- a/bindings/java/Test.java +++ b/bindings/java/Test.java @@ -4,39 +4,45 @@ import com.microsoft.regorus.Engine; public class Test { + public static void main(String[] args) { try (Engine engine = new Engine()) { String pkg = engine.addPolicy( - "hello.rego", - "package test\nx=1\nmessage = concat(\", \", [input.message, data.message])" + "hello.rego", + "package test\nx=1\nmessage = concat(\", \", [input.message, data.message])" ); - System.out.println("Loaded package " + pkg); + System.out.println("Loaded package " + pkg); - engine.addDataJson("{\"message\":\"World!\"}"); engine.setInputJson("{\"message\":\"Hello\"}"); - // Evaluate query. - String resJson = engine.evalQuery("data.test.message"); + // Evaluate query. + String resJson = engine.evalQuery("data.test.message"); System.out.println(resJson); - // Enable coverage. - engine.setEnableCoverage(true); + // Enable coverage. + engine.setEnableCoverage(true); - // Evaluate rule. - String valueJson = engine.evalRule("data.test.message"); + // Evaluate rule. + String valueJson = engine.evalRule("data.test.message"); System.out.println(valueJson); - String coverageJson = engine.getCoverageReport(); - System.out.println(coverageJson); + String coverageJson = engine.getCoverageReport(); + System.out.println(coverageJson); - System.out.println(engine.getCoverageReportPretty()); + System.out.println(engine.getCoverageReportPretty()); - String packagesJson = engine.getPackages(); - System.out.println(packagesJson); + String packagesJson = engine.getPackages(); + System.out.println(packagesJson); - String policiesJson = engine.getPolicies(); - System.out.println(policiesJson); + String policiesJson = engine.getPolicies(); + System.out.println(policiesJson); + + engine.setRegoV0(true); + engine.addPolicy( + "world.rego", + "package world\nx { true }" + ); } } } diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 748be7c..f774eaf 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -28,6 +28,20 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone( Box::into_raw(Box::new(c)) as jlong } +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, + enable: bool, +) { + let _ = throw_err(env, |_env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + engine.set_rego_v0(enable); + Ok(()) + }); +} + #[no_mangle] pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy( env: JNIEnv, diff --git a/bindings/java/src/main/java/com/microsoft/regorus/Engine.java b/bindings/java/src/main/java/com/microsoft/regorus/Engine.java index 20a9727..94d0ba7 100644 --- a/bindings/java/src/main/java/com/microsoft/regorus/Engine.java +++ b/bindings/java/src/main/java/com/microsoft/regorus/Engine.java @@ -8,10 +8,8 @@ package com.microsoft.regorus; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.concurrent.atomic.AtomicReference; /** * Regorus Engine. @@ -23,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable { // if you update the native API. private static native long nativeNewEngine(); private static native long nativeClone(long enginePtr); + private static native void nativeSetRegoV0(long enginePtr, boolean enable); private static native String nativeAddPolicy(long enginePtr, String path, String rego); private static native String nativeAddPolicyFromFile(long enginePtr, String path); private static native String nativeGetPackages(long enginePtr); @@ -55,7 +54,7 @@ public class Engine implements AutoCloseable, Cloneable { Engine(long ptr) { - enginePtr = ptr; + enginePtr = ptr; } /** @@ -65,6 +64,16 @@ public class Engine implements AutoCloseable, Cloneable { return new Engine(nativeClone(enginePtr)); } + /** + * Enable/disable Rego v0. + * + * @param enable Whether to enable v0 or not. + * + */ + public void setRegoV0(boolean enable) { + nativeSetRegoV0(enginePtr, enable); + } + /** * Adds an inline Rego policy. * diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index 0a46cd7..6849de2 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "regoruspy" -version = "0.2.2" +version = "0.3.0" edition = "2021" repository = "https://github.com/microsoft/regorus/bindings/python" description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index ce1f362..b8f552d 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -168,6 +168,15 @@ impl Engine { } } + /// Turn on rego v0. + /// + /// Regorus now defaults to v1. + /// + /// * `enable`: Whether to enable/disable v0. + pub fn set_rego_v0(&mut self, enable: bool) { + self.engine.set_rego_v0(enable) + } + /// Add a policy /// /// The policy is parsed into AST. diff --git a/bindings/python/test.py b/bindings/python/test.py index 6ea6163..f80119e 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -6,6 +6,8 @@ import regorus # Create engine engine = regorus.Engine() +engine.set_rego_v0(True) + # Load policies pkg = engine.add_policy_from_file('../../tests/aci/framework.rego') print(' Loaded package %s' % pkg) diff --git a/bindings/ruby/Gemfile.lock b/bindings/ruby/Gemfile.lock index a7be32e..27a3520 100644 --- a/bindings/ruby/Gemfile.lock +++ b/bindings/ruby/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - regorusrb (0.2.3) + regorusrb (0.3.0) rb_sys (~> 0.9.111) GEM diff --git a/bindings/ruby/README.md b/bindings/ruby/README.md index 53f2b3b..eda7999 100644 --- a/bindings/ruby/README.md +++ b/bindings/ruby/README.md @@ -43,6 +43,9 @@ require "regorus" engine = Regorus::Engine.new +# Policy is old-style. +engine.set_rego_v0(true) + engine.add_policy_from_file('../../tests/aci/framework.rego') engine.add_policy_from_file('../../tests/aci/api.rego') engine.add_policy_from_file('../../tests/aci/policy.rego') diff --git a/bindings/ruby/ext/regorusrb/Cargo.toml b/bindings/ruby/ext/regorusrb/Cargo.toml index 3e0e7b5..22775fc 100644 --- a/bindings/ruby/ext/regorusrb/Cargo.toml +++ b/bindings/ruby/ext/regorusrb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "regorusrb" -version = "0.2.3" +version = "0.3.0" edition = "2024" description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" publish = false diff --git a/bindings/ruby/ext/regorusrb/src/lib.rs b/bindings/ruby/ext/regorusrb/src/lib.rs index 43b0cac..138ec99 100644 --- a/bindings/ruby/ext/regorusrb/src/lib.rs +++ b/bindings/ruby/ext/regorusrb/src/lib.rs @@ -36,6 +36,11 @@ impl Engine { } } + fn set_rego_v0(&self, enable: bool) -> Result<(), Error> { + self.engine.borrow_mut().set_rego_v0(enable); + Ok(()) + } + fn add_policy(&self, path: String, rego: String) -> Result { self.engine .borrow_mut() @@ -297,6 +302,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> { // defines <, <=, >, >=, and == based on <=> engine_class.include_module(module::comparable())?; + // rego language configuration + engine_class.define_method("set_rego_v0", method!(Engine::set_rego_v0, 1))?; + // policy operations engine_class.define_method("add_policy", method!(Engine::add_policy, 2))?; engine_class.define_method( diff --git a/bindings/ruby/lib/regorus/version.rb b/bindings/ruby/lib/regorus/version.rb index a709f6b..d278536 100644 --- a/bindings/ruby/lib/regorus/version.rb +++ b/bindings/ruby/lib/regorus/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Regorus - VERSION = "0.2.3" + VERSION = "0.3.0" end diff --git a/bindings/ruby/test/test_regorus.rb b/bindings/ruby/test/test_regorus.rb index 30ba3c7..5f37baa 100644 --- a/bindings/ruby/test/test_regorus.rb +++ b/bindings/ruby/test/test_regorus.rb @@ -17,11 +17,11 @@ class TestRegorus < Minitest::Test def example_policy <<~REGO package regorus_test - is_manager { + is_manager if { input.name == data.managers[_] } - is_employee { + is_employee if { input.name == data.employees[_] } @@ -29,11 +29,11 @@ class TestRegorus < Minitest::Test default is_manager_bool = false default is_employee_bool = false - is_manager_bool { + is_manager_bool if { is_manager } - is_employee_bool { + is_employee_bool if { is_employee } REGO diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 3a11837..3fdebe4 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "regorusjs" -version = "0.2.2" +version = "0.3.0" edition = "2021" repository = "https://github.com/microsoft/regorus/bindings/wasm" description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 2aaf600..e0a0816 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -45,6 +45,15 @@ impl Engine { } } + /// Turn on rego v0. + /// + /// Regorus defaults to rego v1. + /// + /// * `enable`: Whether to enable or disable rego v0. + pub fn setRegoV0(&mut self, enable: bool) { + self.engine.set_rego_v0(enable) + } + /// Add a policy /// /// The policy is parsed into AST. diff --git a/examples/regorus.rs b/examples/regorus.rs index b4a46ae..17e8b8e 100644 --- a/examples/regorus.rs +++ b/examples/regorus.rs @@ -42,7 +42,7 @@ fn rego_eval( enable_tracing: bool, non_strict: bool, #[cfg(feature = "coverage")] coverage: bool, - v1: bool, + v0: bool, ) -> Result<()> { // Create engine. let mut engine = regorus::Engine::new(); @@ -52,7 +52,7 @@ fn rego_eval( #[cfg(feature = "coverage")] engine.set_enable_coverage(coverage); - engine.set_rego_v1(v1); + engine.set_rego_v0(v0); // Load files from given bundles. for dir in bundles.iter() { @@ -238,9 +238,9 @@ enum RegorusCommand { #[arg(long, short)] coverage: bool, - /// Turn on rego.v1 + /// Turn on Rego language v0. #[arg(long)] - v1: bool, + v0: bool, }, /// Tokenize a Rego policy. @@ -282,7 +282,7 @@ fn main() -> Result<()> { non_strict, #[cfg(feature = "coverage")] coverage, - v1, + v0, } => rego_eval( &bundles, &data, @@ -292,7 +292,7 @@ fn main() -> Result<()> { non_strict, #[cfg(feature = "coverage")] coverage, - v1, + v0, ), RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose), RegorusCommand::Parse { file } => rego_parse(file), diff --git a/examples/server/allowed_server.rego b/examples/server/allowed_server.rego index acd62da..b8c0e79 100644 --- a/examples/server/allowed_server.rego +++ b/examples/server/allowed_server.rego @@ -2,22 +2,22 @@ package example default allow := false # unless otherwise defined, allow is false -allow := true { # allow is true if... +allow := true if { # allow is true if... count(violation) == 0 # there are zero violations. } -violation[server.id] { # a server is in the violation set if... +violation[server.id] if { # a server is in the violation set if... some server public_server[server] # it exists in the 'public_server' set and... server.protocols[_] == "http" # it contains the insecure "http" protocol. } -violation[server.id] { # a server is in the violation set if... +violation[server.id] if { # a server is in the violation set if... server := input.servers[_] # it exists in the input.servers collection and... server.protocols[_] == "telnet" # it contains the "telnet" protocol. } -public_server[server] { # a server exists in the public_server set if... +public_server[server]if { # a server exists in the public_server set if... some i, j server := input.servers[_] # it exists in the input.servers collection and... server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and... diff --git a/src/builtins/units.rs b/src/builtins/units.rs index fec7bee..46af0ce 100644 --- a/src/builtins/units.rs +++ b/src/builtins/units.rs @@ -83,8 +83,8 @@ fn parse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re bail!(span.error("spaces not allowed in resource strings")); } - let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) { - Some(p) => (&string[0..p], &string[p..]), + let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) { + Some(p) => (&string[0..p + 1], &string[p + 1..]), _ => (string, ""), }; @@ -161,8 +161,8 @@ fn parse_bytes(span: &Span, params: &[Ref], args: &[Value], strict: bool) bail!(span.error("spaces not allowed in resource strings")); } - let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) { - Some(p) => (&string[0..p], &string[p..]), + let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) { + Some(p) => (&string[0..p + 1], &string[p + 1..]), _ => (string, ""), }; diff --git a/src/engine.rs b/src/engine.rs index f3ae13d..fd69575 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -37,33 +37,37 @@ impl Engine { modules: vec![], interpreter: Interpreter::new(), prepared: false, - rego_v1: false, + rego_v1: true, } } - /// Turn rego.v1 on/off for subsequently added policies. - /// - /// Explicit import rego.v1 is not needed if set. + /// Enable rego v0. /// + /// Note that regorus now defaults to v1. /// ``` /// # use regorus::*; /// # fn main() -> anyhow::Result<()> { /// let mut engine = Engine::new(); /// - /// engine.set_rego_v1(true); + /// // Enable v0 for old style policies. + /// engine.set_rego_v0(true); + /// /// engine.add_policy( /// "test.rego".to_string(), /// r#" /// package test - /// allow if true # if keyword is automatically imported + /// + /// allow { # v0 syntax does not require if keyword + /// 1 < 2 + /// } /// "#.to_string())?; /// /// # Ok(()) /// # } /// ``` /// - pub fn set_rego_v1(&mut self, rego_v1: bool) { - self.rego_v1 = rego_v1; + pub fn set_rego_v0(&mut self, rego_v0: bool) { + self.rego_v1 = !rego_v0; } /// Add a policy. @@ -114,6 +118,8 @@ impl Engine { /// # use regorus::*; /// # fn main() -> anyhow::Result<()> { /// let mut engine = Engine::new(); + /// // framework.rego does not conform to v1. + /// engine.set_rego_v0(true); /// /// let package = engine.add_policy_from_file("tests/aci/framework.rego")?; /// @@ -139,6 +145,8 @@ impl Engine { /// # use regorus::*; /// # fn main() -> anyhow::Result<()> { /// let mut engine = Engine::new(); + /// // framework.rego does not conform to v1. + /// engine.set_rego_v0(true); /// /// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?; /// @@ -414,6 +422,7 @@ impl Engine { /// let mut engine = Engine::new(); /// /// // Add policies + /// engine.set_rego_v0(true); /// engine.add_policy_from_file("tests/aci/framework.rego")?; /// engine.add_policy_from_file("tests/aci/api.rego")?; /// engine.add_policy_from_file("tests/aci/policy.rego")?; @@ -739,7 +748,7 @@ impl Engine { /// engine.add_policy( /// "policy.rego".to_string(), /// r#"package invalid - /// x = y { + /// x = y if { /// # y = do_magic(2) /// do_magic(2, y) # y is supplied as an out parameter. /// } @@ -775,7 +784,7 @@ impl Engine { /// r#" /// package test # Line 2 /// - /// x = y { # Line 4 + /// x = y if { # Line 4 /// input.a > 2 # Line 5 /// y = 5 # Line 6 /// } diff --git a/src/parser.rs b/src/parser.rs index 5d73df0..80ec850 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -19,7 +19,7 @@ pub struct Parser<'source> { tok: Token, line: u32, end: u32, - future_keywords: BTreeMap, + future_keywords: BTreeMap>, rego_v1: bool, } @@ -41,13 +41,13 @@ impl<'source> Parser<'source> { } pub fn enable_rego_v1(&mut self) -> Result<()> { - self.turn_on_rego_v1(self.tok.1.clone()) + self.turn_on_rego_v1(&None) } - fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> { + fn turn_on_rego_v1(&mut self, span: &Option) -> Result<()> { self.rego_v1 = true; for kw in FUTURE_KEYWORDS { - self.set_future_keyword(kw, &span)?; + self.set_future_keyword(kw, span)?; } Ok(()) } @@ -97,9 +97,9 @@ impl<'source> Parser<'source> { } } - pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> { - match &self.future_keywords.get(kw) { - Some(s) if self.rego_v1 => Err(self.source.error( + pub fn set_future_keyword(&mut self, kw: &str, span: &Option) -> Result<()> { + match (span, self.future_keywords.get(kw)) { + (Some(span), Some(Some(s))) if self.rego_v1 => Err(self.source.error( span.line, span.col, format!( @@ -155,11 +155,11 @@ impl<'source> Parser<'source> { fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result { if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" { match comps.len() - 2 { - 1 => self.set_future_keyword(comps[2].text(), &comps[2])?, + 1 => self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))?, 0 => { let span = &comps[1]; for kw in FUTURE_KEYWORDS.iter() { - self.set_future_keyword(kw, span)?; + self.set_future_keyword(kw, &Some(span.clone()))?; } } _ => { @@ -1709,7 +1709,7 @@ impl<'source> Parser<'source> { let is_future_kw = if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" { - self.turn_on_rego_v1(span.clone())?; + self.turn_on_rego_v1(&Some(span.clone()))?; true } else { self.handle_import_future_keywords(&comps)? diff --git a/src/tests/interpreter/mod.rs b/src/tests/interpreter/mod.rs index 2abe00e..9d41c4f 100644 --- a/src/tests/interpreter/mod.rs +++ b/src/tests/interpreter/mod.rs @@ -141,6 +141,7 @@ pub fn eval_file( strict: bool, ) -> Result<(Vec, Vec)> { let mut engine: Engine = Engine::new(); + engine.set_rego_v0(true); engine.set_strict_builtin_errors(strict); engine.set_gather_prints(true); diff --git a/tests/aci/main.rs b/tests/aci/main.rs index 3b77313..86d6bd9 100644 --- a/tests/aci/main.rs +++ b/tests/aci/main.rs @@ -27,6 +27,7 @@ struct YamlTest { fn eval_test_case(dir: &Path, case: &TestCase) -> Result { let mut engine = Engine::new(); + engine.set_rego_v0(true); engine.add_data(case.data.clone())?; engine.set_input(case.input.clone()); @@ -116,6 +117,7 @@ fn run_aci_tests(dir: &Path) -> Result<()> { #[cfg(feature = "coverage")] fn run_aci_tests_coverage(dir: &Path) -> Result<()> { let mut engine = Engine::new(); + engine.set_rego_v0(true); engine.set_enable_coverage(true); let mut added = std::collections::BTreeSet::new(); diff --git a/tests/coverage/mod.rs b/tests/coverage/mod.rs index 188e274..2c2a573 100644 --- a/tests/coverage/mod.rs +++ b/tests/coverage/mod.rs @@ -45,6 +45,7 @@ fn yaml_test_impl(file: &str) -> Result<()> { let mut engine = Engine::new(); engine.set_enable_coverage(true); + engine.set_rego_v0(true); for (idx, rego) in case.modules.iter().enumerate() { engine.add_policy(format!("rego_{idx}"), rego.clone())?; diff --git a/tests/kata/main.rs b/tests/kata/main.rs index 9b96235..a6e2186 100644 --- a/tests/kata/main.rs +++ b/tests/kata/main.rs @@ -9,6 +9,16 @@ use anyhow::{bail, Result}; use clap::Parser; use walkdir::WalkDir; +fn normalize_printed_paths(mut prints: Vec) -> Vec { + prints.iter_mut().for_each(|p| { + *p = p + .replace("\\", "/") + .replace("//", "/") + .replace("\r\n", "\n"); + }); + prints +} + fn run_kata_tests( tests_dir: &Path, name: &Option, @@ -43,6 +53,7 @@ fn run_kata_tests( let prints_file = path.join("prints.json"); let mut engine = Engine::new(); + engine.set_rego_v0(true); engine.add_policy_from_file(&policy_file)?; engine.set_gather_prints(true); engine.set_strict_builtin_errors(false); @@ -110,14 +121,14 @@ fn run_kata_tests( if generate { results.push(r); - prints.push(engine.take_prints()?); + prints.push(normalize_printed_paths(engine.take_prints()?)); } else { let expected = results.pop().unwrap(); assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display()); - let p = engine.take_prints()?; - assert_eq!(p, new_engine.take_prints()?); - assert_eq!(p, prints.pop().unwrap()); + let p = normalize_printed_paths(engine.take_prints()?); + assert_eq!(p, normalize_printed_paths(new_engine.take_prints()?)); + assert_eq!(p, normalize_printed_paths(prints.pop().unwrap())); } num_queries += 2; @@ -179,7 +190,6 @@ fn stateful_policy_test() -> Result<()> { let policy = String::from( r#" package example - import rego.v1 default allow := false diff --git a/tests/opa.rs b/tests/opa.rs index a021f95..e46bd37 100644 --- a/tests/opa.rs +++ b/tests/opa.rs @@ -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.70.0"; +const OPA_BRANCH: &str = "v1.2.0"; #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(deny_unknown_fields)] @@ -51,13 +51,13 @@ struct YamlTest { cases: Vec, } -fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result { +fn eval_test_case(case: &TestCase, is_rego_v0_test: bool) -> Result { let mut engine = Engine::new(); #[cfg(feature = "coverage")] engine.set_enable_coverage(true); - engine.set_rego_v1(is_rego_v1_test); + engine.set_rego_v0(is_rego_v0_test); if let Some(data) = &case.data { engine.add_data(data.clone())?; @@ -174,7 +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/") || path_dir.starts_with("v1\\"); + let is_rego_v0_test = path_dir_str.starts_with("v0/") || path_dir.starts_with("v0\\"); let entry = status.entry(path_dir_str).or_insert((0, 0, 0)); let yaml_str = std::fs::read_to_string(&path_str)?; @@ -219,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, is_rego_v1_test), &case.want_result) { + match (eval_test_case(&case, is_rego_v0_test), &case.want_result) { (Ok(actual), Some(expected)) if is_json_schema_test && json_schema_tests_check(&actual, &expected) => { @@ -294,6 +294,10 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> { } } + if is_rego_v0_test { + cmd += " -v0"; + } + std::fs::write(path.join(format!("query{n}.text")), case.query.as_bytes())?; cmd += format!(" \"{}\"", &case.query).as_str(); @@ -394,7 +398,7 @@ fn main() -> Result<()> { bail!("failed to clone OPA repository"); } } - format!("{branch_dir}/test/cases/testdata") + format!("{branch_dir}/v1/test/cases/testdata") } };