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 <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-03-10 11:56:01 -07:00
committed by GitHub
parent cbd772623a
commit c963e477a3
36 changed files with 244 additions and 91 deletions

View File

@@ -9,8 +9,8 @@
Regorus is also Regorus is also
- *cross-platform* - Written in platform-agnostic Rust. - *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. - *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`. - *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 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. - *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. - *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). See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352).
Support for extensibility using other languages coming soon. Support for extensibility using other languages coming soon.
@@ -32,7 +32,6 @@ fn main() -> anyhow::Result<()> {
let policy = String::from( let policy = String::from(
r#" r#"
package example package example
import rego.v1
allow if { allow if {
## All actions are allowed for admins. ## 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* -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. builtins. See [OPA Conformance](#opa-conformance) below.
## Bindings ## Bindings
@@ -276,7 +275,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
``` ```
## OPA Conformance ## 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. 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: The test driver can be invoked by running:

View File

@@ -27,7 +27,7 @@ char* file_to_string(const char* file) {
// If regorus is built with custom-allocator, then provide implementation. // If regorus is built with custom-allocator, then provide implementation.
uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) { 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) { void regorus_free(uint8_t* ptr) {
@@ -41,6 +41,11 @@ int main() {
RegorusResult r; RegorusResult r;
char* buffer = NULL; 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. // Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego"))); r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer); free(buffer);

View File

@@ -6,6 +6,11 @@ int main() {
RegorusEngine* engine = regorus_engine_new(); RegorusEngine* engine = regorus_engine_new();
RegorusResult r; 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. // Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego"); r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != RegorusStatusOk) if (r.status != RegorusStatusOk)

View File

@@ -6,6 +6,7 @@ void example()
// Create engine // Create engine
regorus::Engine engine; regorus::Engine engine;
engine.set_rego_v0(true);
engine.set_enable_coverage(true); engine.set_enable_coverage(true);
// Add policies. // Add policies.
@@ -83,6 +84,7 @@ int main() {
// Create engine. // Create engine.
regorus::Engine engine; regorus::Engine engine;
engine.set_rego_v0(true);
// Load policies. // Load policies.

View File

@@ -54,6 +54,9 @@ namespace regorus {
return std::unique_ptr<Engine>(new Engine(regorus_engine_clone(engine))); return std::unique_ptr<Engine>(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) { Result add_policy(const char* path, const char* policy) {
return Result(regorus_engine_add_policy(engine, path, policy)); return Result(regorus_engine_add_policy(engine, path, policy));

View File

@@ -62,6 +62,13 @@ namespace Microsoft.WindowsAzure.Regorus.IaaS
{ {
return Encoding.UTF8.GetBytes(s + char.MinValue); 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) public void AddPolicy(string path, string rego)
{ {

View File

@@ -23,6 +23,7 @@ var w = new Stopwatch();
w.Restart(); w.Restart();
var engine = new Regorus.Engine(); var engine = new Regorus.Engine();
engine.SetRegoV0(true);
w.Stop(); w.Stop();
var newEngineTicks = w.ElapsedTicks; var newEngineTicks = w.ElapsedTicks;

View File

@@ -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) public string AddPolicyFromFile(string path)
{ {
var pathBytes = NullTerminatedUTF8Bytes(path); var pathBytes = NullTerminatedUTF8Bytes(path);

View File

@@ -222,7 +222,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file(
/// Clear policy data. /// 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] #[no_mangle]
pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult { pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> RegorusResult {
to_regorus_result(|| -> Result<()> { to_regorus_result(|| -> Result<()> {
@@ -233,7 +233,7 @@ pub extern "C" fn regorus_engine_clear_data(engine: *mut RegorusEngine) -> Regor
/// Set input. /// 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. /// * `input`: JSON encoded value to be used as input to query.
#[no_mangle] #[no_mangle]
pub extern "C" fn regorus_engine_set_input_json( 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. /// 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. /// * `query`: Rego expression to be evaluate.
#[no_mangle] #[no_mangle]
pub extern "C" fn regorus_engine_eval_query( pub extern "C" fn regorus_engine_eval_query(
@@ -289,7 +289,7 @@ pub extern "C" fn regorus_engine_eval_query(
/// Evaluate specified rule. /// 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. /// * `rule`: Path to the rule.
#[no_mangle] #[no_mangle]
pub extern "C" fn regorus_engine_eval_rule( pub extern "C" fn regorus_engine_eval_rule(
@@ -314,7 +314,7 @@ pub extern "C" fn regorus_engine_eval_rule(
/// Enable/disable coverage. /// 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. /// * `enable`: Whether to enable or disable coverage.
#[no_mangle] #[no_mangle]
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
@@ -330,7 +330,7 @@ pub extern "C" fn regorus_engine_set_enable_coverage(
/// Get coverage report. /// 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] #[no_mangle]
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_get_coverage_report(engine: *mut RegorusEngine) -> RegorusResult { 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. /// 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] #[no_mangle]
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
pub extern "C" fn regorus_engine_clear_coverage_data(engine: *mut RegorusEngine) -> RegorusResult { 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. /// 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. /// * `enable`: Whether to enable or disable gathering print statements.
#[no_mangle] #[no_mangle]
pub extern "C" fn regorus_engine_set_gather_prints( 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. /// 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] #[no_mangle]
pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult { pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult {
let output = || -> Result<String> { let output = || -> Result<String> {
@@ -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")] #[cfg(feature = "custom_allocator")]
extern "C" { extern "C" {
fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8; fn regorus_aligned_alloc(alignment: usize, size: usize) -> *mut u8;

View File

@@ -16,8 +16,11 @@ func main() {
// Create new engine // Create new engine
engine := regorus.NewEngine() engine := regorus.NewEngine()
defer engine.Close() defer engine.Close()
elapsed1 := time.Since(t)
engine.SetRegoV0(true)
elapsed1 := time.Since(t)
t = time.Now() t = time.Now()
// Add policies and data. // Add policies and data.
policies := []string{ policies := []string{

View File

@@ -28,6 +28,17 @@ func (e *Engine) Clone() *Engine {
return c 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) { func (e *Engine) AddPolicy(path string, rego string) (string, error) {
path_c := C.CString(path) path_c := C.CString(path)
defer C.free(unsafe.Pointer(path_c)) defer C.free(unsafe.Pointer(path_c))

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "regorus-java" name = "regorus-java"
version = "0.2.2" version = "0.3.0"
edition = "2021" edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/java" repository = "https://github.com/microsoft/regorus/bindings/java"
description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" description = "Java bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"

View File

@@ -4,39 +4,45 @@
import com.microsoft.regorus.Engine; import com.microsoft.regorus.Engine;
public class Test { public class Test {
public static void main(String[] args) { public static void main(String[] args) {
try (Engine engine = new Engine()) { try (Engine engine = new Engine()) {
String pkg = engine.addPolicy( String pkg = engine.addPolicy(
"hello.rego", "hello.rego",
"package test\nx=1\nmessage = concat(\", \", [input.message, data.message])" "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.addDataJson("{\"message\":\"World!\"}");
engine.setInputJson("{\"message\":\"Hello\"}"); engine.setInputJson("{\"message\":\"Hello\"}");
// Evaluate query. // Evaluate query.
String resJson = engine.evalQuery("data.test.message"); String resJson = engine.evalQuery("data.test.message");
System.out.println(resJson); System.out.println(resJson);
// Enable coverage. // Enable coverage.
engine.setEnableCoverage(true); engine.setEnableCoverage(true);
// Evaluate rule. // Evaluate rule.
String valueJson = engine.evalRule("data.test.message"); String valueJson = engine.evalRule("data.test.message");
System.out.println(valueJson); System.out.println(valueJson);
String coverageJson = engine.getCoverageReport(); String coverageJson = engine.getCoverageReport();
System.out.println(coverageJson); System.out.println(coverageJson);
System.out.println(engine.getCoverageReportPretty()); System.out.println(engine.getCoverageReportPretty());
String packagesJson = engine.getPackages(); String packagesJson = engine.getPackages();
System.out.println(packagesJson); System.out.println(packagesJson);
String policiesJson = engine.getPolicies(); String policiesJson = engine.getPolicies();
System.out.println(policiesJson); System.out.println(policiesJson);
engine.setRegoV0(true);
engine.addPolicy(
"world.rego",
"package world\nx { true }"
);
} }
} }
} }

View File

@@ -28,6 +28,20 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
Box::into_raw(Box::new(c)) as jlong 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] #[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy( pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: JNIEnv, env: JNIEnv,

View File

@@ -8,10 +8,8 @@ package com.microsoft.regorus;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Regorus Engine. * Regorus Engine.
@@ -23,6 +21,7 @@ public class Engine implements AutoCloseable, Cloneable {
// if you update the native API. // if you update the native API.
private static native long nativeNewEngine(); private static native long nativeNewEngine();
private static native long nativeClone(long enginePtr); 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 nativeAddPolicy(long enginePtr, String path, String rego);
private static native String nativeAddPolicyFromFile(long enginePtr, String path); private static native String nativeAddPolicyFromFile(long enginePtr, String path);
private static native String nativeGetPackages(long enginePtr); private static native String nativeGetPackages(long enginePtr);
@@ -55,7 +54,7 @@ public class Engine implements AutoCloseable, Cloneable {
Engine(long ptr) { Engine(long ptr) {
enginePtr = ptr; enginePtr = ptr;
} }
/** /**
@@ -65,6 +64,16 @@ public class Engine implements AutoCloseable, Cloneable {
return new Engine(nativeClone(enginePtr)); 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. * Adds an inline Rego policy.
* *

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "regoruspy" name = "regoruspy"
version = "0.2.2" version = "0.3.0"
edition = "2021" edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/python" repository = "https://github.com/microsoft/regorus/bindings/python"
description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" description = "Python bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"

View File

@@ -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 /// Add a policy
/// ///
/// The policy is parsed into AST. /// The policy is parsed into AST.

View File

@@ -6,6 +6,8 @@ import regorus
# Create engine # Create engine
engine = regorus.Engine() engine = regorus.Engine()
engine.set_rego_v0(True)
# Load policies # Load policies
pkg = engine.add_policy_from_file('../../tests/aci/framework.rego') pkg = engine.add_policy_from_file('../../tests/aci/framework.rego')
print(' Loaded package %s' % pkg) print(' Loaded package %s' % pkg)

View File

@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
regorusrb (0.2.3) regorusrb (0.3.0)
rb_sys (~> 0.9.111) rb_sys (~> 0.9.111)
GEM GEM

View File

@@ -43,6 +43,9 @@ require "regorus"
engine = Regorus::Engine.new 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/framework.rego')
engine.add_policy_from_file('../../tests/aci/api.rego') engine.add_policy_from_file('../../tests/aci/api.rego')
engine.add_policy_from_file('../../tests/aci/policy.rego') engine.add_policy_from_file('../../tests/aci/policy.rego')

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "regorusrb" name = "regorusrb"
version = "0.2.3" version = "0.3.0"
edition = "2024" edition = "2024"
description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" description = "Ruby bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"
publish = false publish = false

View File

@@ -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<String, Error> { fn add_policy(&self, path: String, rego: String) -> Result<String, Error> {
self.engine self.engine
.borrow_mut() .borrow_mut()
@@ -297,6 +302,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
// defines <, <=, >, >=, and == based on <=> // defines <, <=, >, >=, and == based on <=>
engine_class.include_module(module::comparable())?; engine_class.include_module(module::comparable())?;
// rego language configuration
engine_class.define_method("set_rego_v0", method!(Engine::set_rego_v0, 1))?;
// policy operations // policy operations
engine_class.define_method("add_policy", method!(Engine::add_policy, 2))?; engine_class.define_method("add_policy", method!(Engine::add_policy, 2))?;
engine_class.define_method( engine_class.define_method(

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module Regorus module Regorus
VERSION = "0.2.3" VERSION = "0.3.0"
end end

View File

@@ -17,11 +17,11 @@ class TestRegorus < Minitest::Test
def example_policy def example_policy
<<~REGO <<~REGO
package regorus_test package regorus_test
is_manager { is_manager if {
input.name == data.managers[_] input.name == data.managers[_]
} }
is_employee { is_employee if {
input.name == data.employees[_] input.name == data.employees[_]
} }
@@ -29,11 +29,11 @@ class TestRegorus < Minitest::Test
default is_manager_bool = false default is_manager_bool = false
default is_employee_bool = false default is_employee_bool = false
is_manager_bool { is_manager_bool if {
is_manager is_manager
} }
is_employee_bool { is_employee_bool if {
is_employee is_employee
} }
REGO REGO

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "regorusjs" name = "regorusjs"
version = "0.2.2" version = "0.3.0"
edition = "2021" edition = "2021"
repository = "https://github.com/microsoft/regorus/bindings/wasm" repository = "https://github.com/microsoft/regorus/bindings/wasm"
description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust" description = "WASM bindings for Regorus - a fast, lightweight Rego interpreter written in Rust"

View File

@@ -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 /// Add a policy
/// ///
/// The policy is parsed into AST. /// The policy is parsed into AST.

View File

@@ -42,7 +42,7 @@ fn rego_eval(
enable_tracing: bool, enable_tracing: bool,
non_strict: bool, non_strict: bool,
#[cfg(feature = "coverage")] coverage: bool, #[cfg(feature = "coverage")] coverage: bool,
v1: bool, v0: bool,
) -> Result<()> { ) -> Result<()> {
// Create engine. // Create engine.
let mut engine = regorus::Engine::new(); let mut engine = regorus::Engine::new();
@@ -52,7 +52,7 @@ fn rego_eval(
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
engine.set_enable_coverage(coverage); engine.set_enable_coverage(coverage);
engine.set_rego_v1(v1); engine.set_rego_v0(v0);
// Load files from given bundles. // Load files from given bundles.
for dir in bundles.iter() { for dir in bundles.iter() {
@@ -238,9 +238,9 @@ enum RegorusCommand {
#[arg(long, short)] #[arg(long, short)]
coverage: bool, coverage: bool,
/// Turn on rego.v1 /// Turn on Rego language v0.
#[arg(long)] #[arg(long)]
v1: bool, v0: bool,
}, },
/// Tokenize a Rego policy. /// Tokenize a Rego policy.
@@ -282,7 +282,7 @@ fn main() -> Result<()> {
non_strict, non_strict,
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
coverage, coverage,
v1, v0,
} => rego_eval( } => rego_eval(
&bundles, &bundles,
&data, &data,
@@ -292,7 +292,7 @@ fn main() -> Result<()> {
non_strict, non_strict,
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
coverage, coverage,
v1, v0,
), ),
RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose), RegorusCommand::Lex { file, verbose } => rego_lex(file, verbose),
RegorusCommand::Parse { file } => rego_parse(file), RegorusCommand::Parse { file } => rego_parse(file),

View File

@@ -2,22 +2,22 @@ package example
default allow := false # unless otherwise defined, allow is false 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. 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 some server
public_server[server] # it exists in the 'public_server' set and... public_server[server] # it exists in the 'public_server' set and...
server.protocols[_] == "http" # it contains the insecure "http" protocol. 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 := input.servers[_] # it exists in the input.servers collection and...
server.protocols[_] == "telnet" # it contains the "telnet" protocol. 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 some i, j
server := input.servers[_] # it exists in the input.servers collection and... 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... server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and...

View File

@@ -83,8 +83,8 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
bail!(span.error("spaces not allowed in resource strings")); bail!(span.error("spaces not allowed in resource strings"));
} }
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) { let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p], &string[p..]), Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""), _ => (string, ""),
}; };
@@ -161,8 +161,8 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
bail!(span.error("spaces not allowed in resource strings")); bail!(span.error("spaces not allowed in resource strings"));
} }
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) { let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p], &string[p..]), Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""), _ => (string, ""),
}; };

View File

@@ -37,33 +37,37 @@ impl Engine {
modules: vec![], modules: vec![],
interpreter: Interpreter::new(), interpreter: Interpreter::new(),
prepared: false, prepared: false,
rego_v1: false, rego_v1: true,
} }
} }
/// Turn rego.v1 on/off for subsequently added policies. /// Enable rego v0.
///
/// Explicit import rego.v1 is not needed if set.
/// ///
/// Note that regorus now defaults to v1.
/// ``` /// ```
/// # use regorus::*; /// # use regorus::*;
/// # fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// engine.set_rego_v1(true); /// // Enable v0 for old style policies.
/// engine.set_rego_v0(true);
///
/// engine.add_policy( /// engine.add_policy(
/// "test.rego".to_string(), /// "test.rego".to_string(),
/// r#" /// r#"
/// package test /// package test
/// allow if true # if keyword is automatically imported ///
/// allow { # v0 syntax does not require if keyword
/// 1 < 2
/// }
/// "#.to_string())?; /// "#.to_string())?;
/// ///
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
/// ///
pub fn set_rego_v1(&mut self, rego_v1: bool) { pub fn set_rego_v0(&mut self, rego_v0: bool) {
self.rego_v1 = rego_v1; self.rego_v1 = !rego_v0;
} }
/// Add a policy. /// Add a policy.
@@ -114,6 +118,8 @@ impl Engine {
/// # use regorus::*; /// # use regorus::*;
/// # fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new(); /// 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")?; /// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
/// ///
@@ -139,6 +145,8 @@ impl Engine {
/// # use regorus::*; /// # use regorus::*;
/// # fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new(); /// 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")?; /// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
/// ///
@@ -414,6 +422,7 @@ impl Engine {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Add policies /// // Add policies
/// engine.set_rego_v0(true);
/// engine.add_policy_from_file("tests/aci/framework.rego")?; /// 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/api.rego")?;
/// engine.add_policy_from_file("tests/aci/policy.rego")?; /// engine.add_policy_from_file("tests/aci/policy.rego")?;
@@ -739,7 +748,7 @@ impl Engine {
/// engine.add_policy( /// engine.add_policy(
/// "policy.rego".to_string(), /// "policy.rego".to_string(),
/// r#"package invalid /// r#"package invalid
/// x = y { /// x = y if {
/// # y = do_magic(2) /// # y = do_magic(2)
/// do_magic(2, y) # y is supplied as an out parameter. /// do_magic(2, y) # y is supplied as an out parameter.
/// } /// }
@@ -775,7 +784,7 @@ impl Engine {
/// r#" /// r#"
/// package test # Line 2 /// package test # Line 2
/// ///
/// x = y { # Line 4 /// x = y if { # Line 4
/// input.a > 2 # Line 5 /// input.a > 2 # Line 5
/// y = 5 # Line 6 /// y = 5 # Line 6
/// } /// }

View File

@@ -19,7 +19,7 @@ pub struct Parser<'source> {
tok: Token, tok: Token,
line: u32, line: u32,
end: u32, end: u32,
future_keywords: BTreeMap<String, Span>, future_keywords: BTreeMap<String, Option<Span>>,
rego_v1: bool, rego_v1: bool,
} }
@@ -41,13 +41,13 @@ impl<'source> Parser<'source> {
} }
pub fn enable_rego_v1(&mut self) -> Result<()> { 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<Span>) -> Result<()> {
self.rego_v1 = true; self.rego_v1 = true;
for kw in FUTURE_KEYWORDS { for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?; self.set_future_keyword(kw, span)?;
} }
Ok(()) Ok(())
} }
@@ -97,9 +97,9 @@ impl<'source> Parser<'source> {
} }
} }
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> { pub fn set_future_keyword(&mut self, kw: &str, span: &Option<Span>) -> Result<()> {
match &self.future_keywords.get(kw) { match (span, self.future_keywords.get(kw)) {
Some(s) if self.rego_v1 => Err(self.source.error( (Some(span), Some(Some(s))) if self.rego_v1 => Err(self.source.error(
span.line, span.line,
span.col, span.col,
format!( format!(
@@ -155,11 +155,11 @@ impl<'source> Parser<'source> {
fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result<bool> { fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result<bool> {
if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" { if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" {
match comps.len() - 2 { 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 => { 0 => {
let span = &comps[1]; let span = &comps[1];
for kw in FUTURE_KEYWORDS.iter() { 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 = let is_future_kw =
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" { 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 true
} else { } else {
self.handle_import_future_keywords(&comps)? self.handle_import_future_keywords(&comps)?

View File

@@ -141,6 +141,7 @@ pub fn eval_file(
strict: bool, strict: bool,
) -> Result<(Vec<Value>, Vec<String>)> { ) -> Result<(Vec<Value>, Vec<String>)> {
let mut engine: Engine = Engine::new(); let mut engine: Engine = Engine::new();
engine.set_rego_v0(true);
engine.set_strict_builtin_errors(strict); engine.set_strict_builtin_errors(strict);
engine.set_gather_prints(true); engine.set_gather_prints(true);

View File

@@ -27,6 +27,7 @@ struct YamlTest {
fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> { fn eval_test_case(dir: &Path, case: &TestCase) -> Result<Value> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.add_data(case.data.clone())?; engine.add_data(case.data.clone())?;
engine.set_input(case.input.clone()); engine.set_input(case.input.clone());
@@ -116,6 +117,7 @@ fn run_aci_tests(dir: &Path) -> Result<()> {
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
fn run_aci_tests_coverage(dir: &Path) -> Result<()> { fn run_aci_tests_coverage(dir: &Path) -> Result<()> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.set_enable_coverage(true); engine.set_enable_coverage(true);
let mut added = std::collections::BTreeSet::new(); let mut added = std::collections::BTreeSet::new();

View File

@@ -45,6 +45,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_enable_coverage(true); engine.set_enable_coverage(true);
engine.set_rego_v0(true);
for (idx, rego) in case.modules.iter().enumerate() { for (idx, rego) in case.modules.iter().enumerate() {
engine.add_policy(format!("rego_{idx}"), rego.clone())?; engine.add_policy(format!("rego_{idx}"), rego.clone())?;

View File

@@ -9,6 +9,16 @@ use anyhow::{bail, Result};
use clap::Parser; use clap::Parser;
use walkdir::WalkDir; use walkdir::WalkDir;
fn normalize_printed_paths(mut prints: Vec<String>) -> Vec<String> {
prints.iter_mut().for_each(|p| {
*p = p
.replace("\\", "/")
.replace("//", "/")
.replace("\r\n", "\n");
});
prints
}
fn run_kata_tests( fn run_kata_tests(
tests_dir: &Path, tests_dir: &Path,
name: &Option<String>, name: &Option<String>,
@@ -43,6 +53,7 @@ fn run_kata_tests(
let prints_file = path.join("prints.json"); let prints_file = path.join("prints.json");
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_rego_v0(true);
engine.add_policy_from_file(&policy_file)?; engine.add_policy_from_file(&policy_file)?;
engine.set_gather_prints(true); engine.set_gather_prints(true);
engine.set_strict_builtin_errors(false); engine.set_strict_builtin_errors(false);
@@ -110,14 +121,14 @@ fn run_kata_tests(
if generate { if generate {
results.push(r); results.push(r);
prints.push(engine.take_prints()?); prints.push(normalize_printed_paths(engine.take_prints()?));
} else { } else {
let expected = results.pop().unwrap(); let expected = results.pop().unwrap();
assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display()); assert_eq!(r, expected, "{lineno} failed in {}", inputs_file.display());
let p = engine.take_prints()?; let p = normalize_printed_paths(engine.take_prints()?);
assert_eq!(p, new_engine.take_prints()?); assert_eq!(p, normalize_printed_paths(new_engine.take_prints()?));
assert_eq!(p, prints.pop().unwrap()); assert_eq!(p, normalize_printed_paths(prints.pop().unwrap()));
} }
num_queries += 2; num_queries += 2;
@@ -179,7 +190,6 @@ fn stateful_policy_test() -> Result<()> {
let policy = String::from( let policy = String::from(
r#" r#"
package example package example
import rego.v1
default allow := false default allow := false

View File

@@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize};
use walkdir::WalkDir; use walkdir::WalkDir;
const OPA_REPO: &str = "https://github.com/open-policy-agent/opa"; 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)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
@@ -51,13 +51,13 @@ struct YamlTest {
cases: Vec<TestCase>, cases: Vec<TestCase>,
} }
fn eval_test_case(case: &TestCase, is_rego_v1_test: bool) -> Result<Value> { fn eval_test_case(case: &TestCase, is_rego_v0_test: bool) -> Result<Value> {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[cfg(feature = "coverage")] #[cfg(feature = "coverage")]
engine.set_enable_coverage(true); 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 { if let Some(data) = &case.data {
engine.add_data(data.clone())?; engine.add_data(data.clone())?;
@@ -174,7 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
continue; 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 entry = status.entry(path_dir_str).or_insert((0, 0, 0));
let yaml_str = std::fs::read_to_string(&path_str)?; 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); print!("{:4}: {:90}", entry.2, case.note);
entry.2 += 1; 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)) (Ok(actual), Some(expected))
if is_json_schema_test && json_schema_tests_check(&actual, &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())?; std::fs::write(path.join(format!("query{n}.text")), case.query.as_bytes())?;
cmd += format!(" \"{}\"", &case.query).as_str(); cmd += format!(" \"{}\"", &case.query).as_str();
@@ -394,7 +398,7 @@ fn main() -> Result<()> {
bail!("failed to clone OPA repository"); bail!("failed to clone OPA repository");
} }
} }
format!("{branch_dir}/test/cases/testdata") format!("{branch_dir}/v1/test/cases/testdata")
} }
}; };