From 084b2feefc55bce8e182e8ec16d703898ff2228a Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi Date: Wed, 22 May 2024 15:55:32 -0700 Subject: [PATCH] Update bindings to include newer APIs - Java - Python - WASM `arc` feature is turned on for all bindings Use pretty string instead of colored string. Signed-off-by: Anand Krishnamoorthi --- .github/workflows/test-wasm.yml | 1 + bindings/java/Cargo.toml | 5 +- bindings/java/Test.java | 22 ++- bindings/java/com_microsoft_regorus_Engine.h | 72 ++++++- bindings/java/src/lib.rs | 175 +++++++++++++++++- .../java/com/microsoft/regorus/Engine.java | 112 ++++++++++- bindings/python/Cargo.toml | 5 +- bindings/python/README.md | 13 +- bindings/python/src/lib.rs | 75 ++++++-- bindings/python/test.py | 37 ++++ bindings/ruby/ext/regorusrb/Cargo.toml | 5 +- bindings/ruby/lib/regorus/version.rb | 2 +- bindings/wasm/Cargo.toml | 5 +- bindings/wasm/README.md | 66 +------ bindings/wasm/src/lib.rs | 123 +++++++++++- bindings/wasm/test.js | 65 +++++-- examples/regorus.rs | 2 +- src/lib.rs | 4 +- tests/aci/main.rs | 2 +- tests/kata/main.rs | 2 +- 20 files changed, 655 insertions(+), 138 deletions(-) diff --git a/.github/workflows/test-wasm.yml b/.github/workflows/test-wasm.yml index 1a5e309e..6127e905 100644 --- a/.github/workflows/test-wasm.yml +++ b/.github/workflows/test-wasm.yml @@ -27,4 +27,5 @@ jobs: run: | cd bindings/wasm wasm-pack build --target nodejs --release + wasm-pack test --release --node node test.js diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 33b1840f..272cfac5 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -10,8 +10,11 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"] [lib] crate-type = ["cdylib"] +[features] +default = ["regorus/std", "regorus/full-opa"] + [dependencies] anyhow = "1.0" serde_json = "1.0.112" jni = "0.21.1" -regorus = { path = "../.." } +regorus = { path = "../..", default-features = false, features = ["arc"] } diff --git a/bindings/java/Test.java b/bindings/java/Test.java index c36e7fb6..ef713bdc 100644 --- a/bindings/java/Test.java +++ b/bindings/java/Test.java @@ -6,15 +6,31 @@ public class Test { public static void main(String[] args) { try (Engine engine = new Engine()) { - engine.addPolicy( + var pkg = engine.addPolicy( "hello.rego", - "package test\nmessage = concat(\", \", [input.message, data.message])" + "package test\nx=1\nmessage = concat(\", \", [input.message, data.message])" ); + System.out.println("Loaded package " + pkg); + + engine.addDataJson("{\"message\":\"World!\"}"); engine.setInputJson("{\"message\":\"Hello\"}"); - 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); + + // Evaluate rule. + String valueJson = engine.evalRule("data.test.message"); + System.out.println(valueJson); + + var coverage = engine.getCoverageReport(); + System.out.println(coverage); + + System.out.println(engine.getCoverageReportPretty()); } } } diff --git a/bindings/java/com_microsoft_regorus_Engine.h b/bindings/java/com_microsoft_regorus_Engine.h index 86f7e2f1..ec507880 100644 --- a/bindings/java/com_microsoft_regorus_Engine.h +++ b/bindings/java/com_microsoft_regorus_Engine.h @@ -15,20 +15,28 @@ extern "C" { JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeNewEngine (JNIEnv *, jclass); +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeClone + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_com_microsoft_regorus_Engine_nativeClone + (JNIEnv *, jclass, jlong); + /* * Class: com_microsoft_regorus_Engine * Method: nativeAddPolicy - * Signature: (JLjava/lang/String;Ljava/lang/String;)V + * Signature: (JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ -JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicy +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicy (JNIEnv *, jclass, jlong, jstring, jstring); /* * Class: com_microsoft_regorus_Engine * Method: nativeAddPolicyFromFile - * Signature: (JLjava/lang/String;)V + * Signature: (JLjava/lang/String;)Ljava/lang/String; */ -JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile (JNIEnv *, jclass, jlong, jstring); /* @@ -79,6 +87,62 @@ JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromF JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeEvalQuery (JNIEnv *, jclass, jlong, jstring); +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeEvalRule + * Signature: (JLjava/lang/String;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeEvalRule + (JNIEnv *, jclass, jlong, jstring); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeSetEnableCoverage + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage + (JNIEnv *, jclass, jlong, jboolean); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeGetCoverageReport + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeGetCoverageReport + (JNIEnv *, jclass, jlong); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeGetCoverageReportAsColoredString + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeGetCoverageReportAsColoredString + (JNIEnv *, jclass, jlong); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeClearCoverageData + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeClearCoverageData + (JNIEnv *, jclass, jlong); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeSetGatherPrints + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_com_microsoft_regorus_Engine_nativeSetGatherPrints + (JNIEnv *, jclass, jlong, jboolean); + +/* + * Class: com_microsoft_regorus_Engine + * Method: nativeTakePrints + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_microsoft_regorus_Engine_nativeTakePrints + (JNIEnv *, jclass, jlong); + /* * Class: com_microsoft_regorus_Engine * Method: nativeDestroyEngine diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 304ae71c..586b2155 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -17,6 +17,17 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine( Box::into_raw(Box::new(engine)) as jlong } +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone( + _env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jlong { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let c = engine.clone(); + Box::into_raw(Box::new(c)) as jlong +} + #[no_mangle] pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy( env: JNIEnv, @@ -24,14 +35,19 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy( engine_ptr: jlong, path: JString, rego: JString, -) { - let _ = throw_err(env, |env| { +) -> jstring { + let res = throw_err(env, |env| { let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; let path: String = env.get_string(&path)?.into(); let rego: String = env.get_string(®o)?.into(); - engine.add_policy(path, rego)?; - Ok(()) + let pkg = env.new_string(engine.add_policy(path, rego)?)?; + Ok(pkg.into_raw()) }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } } #[no_mangle] @@ -40,13 +56,37 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile _class: JClass, engine_ptr: jlong, path: JString, -) { - let _ = throw_err(env, |env| { +) -> jstring { + let res = throw_err(env, |env| { let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; let path: String = env.get_string(&path)?.into(); - engine.add_policy_from_file(path)?; - Ok(()) + let pkg = env.new_string(engine.add_policy_from_file(path)?)?; + Ok(pkg.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let packages = engine.get_packages()?; + let packages_json = env.new_string(serde_json::to_string_pretty(&packages)?)?; + Ok(packages_json.into_raw()) }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } } #[no_mangle] @@ -143,6 +183,125 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery( } } +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, + rule: JString, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let rule: String = env.get_string(&rule)?.into(); + let value = engine.eval_rule(rule)?; + let output = env.new_string(value.to_json_str()?)?; + Ok(output.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, + enable: bool, +) { + let _ = throw_err(env, |_| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + engine.set_enable_coverage(enable); + Ok(()) + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let report = engine.get_coverage_report()?; + let output = env.new_string(serde_json::to_string_pretty(&report)?)?; + Ok(output.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let report = engine.get_coverage_report()?.to_pretty_string()?; + let output = env.new_string(&report)?; + Ok(output.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) { + let _ = throw_err(env, |_| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + engine.clear_coverage_data(); + Ok(()) + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetGatherPrints( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, + b: bool, +) { + let _ = throw_err(env, |_| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + engine.set_gather_prints(b); + Ok(()) + }); +} + +#[no_mangle] +pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints( + env: JNIEnv, + _class: JClass, + engine_ptr: jlong, +) -> jstring { + let res = throw_err(env, |env| { + let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; + let prints = engine.take_prints()?; + let output = env.new_string(serde_json::to_string_pretty(&prints)?)?; + Ok(output.into_raw()) + }); + + match res { + Ok(val) => val, + Err(_) => JObject::null().into_raw(), + } +} + #[no_mangle] pub unsafe extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine( _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 11fc0724..dc8c2373 100644 --- a/bindings/java/src/main/java/com/microsoft/regorus/Engine.java +++ b/bindings/java/src/main/java/com/microsoft/regorus/Engine.java @@ -16,20 +16,29 @@ /** * Regorus Engine. */ -public class Engine implements AutoCloseable { +public class Engine implements AutoCloseable, Cloneable { // Methods exposed from Rust side, you can run // `javac -h . src/main/java/com/microsoft/regorus/Engine.java` to update // expected native header at `bindings/java/com_microsoft_regorus_Engine.h` // if you update the native API. private static native long nativeNewEngine(); - private static native void nativeAddPolicy(long enginePtr, String path, String rego); - private static native void nativeAddPolicyFromFile(long enginePtr, String path); + private static native long nativeClone(long enginePtr); + 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); private static native void nativeClearData(long enginePtr); private static native void nativeAddDataJson(long enginePtr, String data); private static native void nativeAddDataJsonFromFile(long enginePtr, String path); private static native void nativeSetInputJson(long enginePtr, String input); private static native void nativeSetInputJsonFromFile(long enginePtr, String path); private static native String nativeEvalQuery(long enginePtr, String query); + private static native String nativeEvalRule(long enginePtr, String qrule); + private static native void nativeSetEnableCoverage(long enginePtr, boolean enable); + private static native String nativeGetCoverageReport(long enginePtr); + private static native String nativeGetCoverageReportPretty(long enginePtr); + private static native void nativeClearCoverageData(long enginePtr); + private static native void nativeSetGatherPrints(long enginePtr, boolean b); + private static native String nativeTakePrints(long enginePtr); private static native void nativeDestroyEngine(long enginePtr); // Pointer to Engine allocated on Rust's heap, all native methods works on @@ -43,25 +52,50 @@ public Engine() { enginePtr = nativeNewEngine(); } + + Engine(long ptr) { + enginePtr = ptr; + } + + /** + * Efficiently clones an Engine. + */ + public Engine Clone() { + return new Engine(nativeClone(enginePtr)); + } + /** * Adds an inline Rego policy. * * @param filename Filename of this Rego policy. * @param rego Rego policy. + * + * @return Rego package defined in the policy. */ - public void addPolicy(String filename, String rego) { - nativeAddPolicy(enginePtr, filename, rego); + public String addPolicy(String filename, String rego) { + return nativeAddPolicy(enginePtr, filename, rego); } /** * Adds a Rego policy from given path. * * @param path Path of the Rego policy. + * + * @return Rego package defined in the policy. */ - public void addPolicyFromFile(String path) { - nativeAddPolicyFromFile(enginePtr, path); + public String addPolicyFromFile(String path) { + return nativeAddPolicyFromFile(enginePtr, path); } + /** + * Get list of loaded Rego packages. + * + * @return List of Rego packages as a JSON array of strings. + */ + public String getPackages() { + return nativeGetPackages(enginePtr); + } + /** * Clears the data document. */ @@ -137,6 +171,70 @@ public String evalQuery(String query) { return nativeEvalQuery(enginePtr, query); } + /** + * Evaluates given Rego rule and returns a JSON string as a result. + * + * @param rule Path of the Rego rule. + * + * @return Value of the rule as a JSON string. + */ + public String evalRule(String rule) { + return nativeEvalRule(enginePtr, rule); + } + + /** + * Enable/disable coverage. + * + * @param enable Whether to enable coverage or not. + * + */ + public void setEnableCoverage(boolean enable) { + nativeSetEnableCoverage(enginePtr, enable); + } + + /** + * Clear coverage data. + * + */ + public void clearCoverageData() { + nativeClearCoverageData(enginePtr); + } + + /** + * Get coverage report as json string. + * + */ + public String getCoverageReport() { + return nativeGetCoverageReport(enginePtr); + } + + /** + * Get coverage report as ANSI color coded string. + * + */ + public String getCoverageReportPretty() { + return nativeGetCoverageReportPretty(enginePtr); + } + + /** + * Enable/disable gathering prints. + * + * @param b Whether to gather prints or not. + * + */ + public void setGatherPrints(boolean b) { + nativeSetGatherPrints(enginePtr, b); + } + + /** + * Take gathered prints. + * + */ + public String takePrints() { + return nativeTakePrints(enginePtr); + } + + @Override public void close() { nativeDestroyEngine(enginePtr); diff --git a/bindings/python/Cargo.toml b/bindings/python/Cargo.toml index d62f688d..33875dc2 100644 --- a/bindings/python/Cargo.toml +++ b/bindings/python/Cargo.toml @@ -11,10 +11,13 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"] [lib] crate-type = ["cdylib"] +[features] +default = ["regorus/std", "regorus/full-opa"] + [dependencies] anyhow = "1.0" ordered-float = "4.2.0" pyo3 = {version = "0.21.0", features = ["anyhow", "extension-module"] } -regorus = { path = "../.." } +regorus = { path = "../..", default-features = false, features = ["arc"] } serde_json = "1.0.112" diff --git a/bindings/python/README.md b/bindings/python/README.md index 2e91aa25..20e31b93 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -10,7 +10,7 @@ Regorus can be used in Python via `regorus` package. (It is not yet available in See [Repository](https://github.com/microsoft/regorus). -To build this binding, see [building](https://github.com/microsoft/regorus/bindings/python/building.md) +To build this binding, see [building](https://github.com/microsoft/regorus/blob/main/bindings/python/building.md) ## Usage ```Python @@ -54,14 +54,11 @@ input = { } engine.set_input(input) -# Eval query -results = engine.eval_query('data.framework.mount_overlay=x') +# Eval rule +value = engine.eval_rule('data.framework.mount_overlay') -# Print results -print(results['result'][0]) +# Print value +print(value) -# Eval query as json -results_json = engine.eval_query_as_json('data.framework.mount_overlay=x') -print(results_json) ``` diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 4502cddc..ff83b162 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -21,18 +21,6 @@ impl Default for Engine { } } -impl Clone for Engine { - /// Clone a [`Engine`] - /// - /// To avoid having to parse same policy again, the engine can be cloned - /// after policies and data have been added. - fn clone(&self) -> Self { - Self { - engine: self.engine.clone(), - } - } -} - fn from<'source>(ob: &Bound<'_, PyAny>) -> Result { // dicts Ok(if let Ok(dict) = ob.downcast::() { @@ -299,6 +287,69 @@ impl Engine { let results = self.engine.eval_query(query, false)?; serde_json::to_string_pretty(&results).map_err(|e| anyhow!("{e}")) } + + /// Evaluate rule. + /// + /// * `rule`: Full path to the rule. + pub fn eval_rule(&mut self, rule: String, py: Python<'_>) -> Result { + to(self.engine.eval_rule(rule)?, py) + } + + /// Evaluate rule and return value as json. + /// + /// * `rule`: Full path to the rule. + pub fn eval_rule_as_json(&mut self, rule: String) -> Result { + let v = self.engine.eval_rule(rule)?; + v.to_json_str() + } + + /// Enable code coverage + /// + /// * `enable`: Whether to enable coverage or not. + pub fn set_enable_coverage(&mut self, enable: bool) { + self.engine.set_enable_coverage(enable) + } + + /// Get coverage report as json. + /// + pub fn get_coverage_report_as_json(&self) -> Result { + let report = self.engine.get_coverage_report()?; + serde_json::to_string_pretty(&report).map_err(|e| anyhow!("{e}")) + } + + // Get coverage report as pretty printable string. + /// + pub fn get_coverage_report_pretty(&self) -> Result { + self.engine.get_coverage_report()?.to_pretty_string() + } + + /// Clear coverage data. + /// + pub fn clear_coverage_data(&mut self) { + self.engine.clear_coverage_data(); + } + + /// Gather print statements instead of printing to stderr. + /// + pub fn set_gather_prints(&mut self, b: bool) { + self.engine.set_gather_prints(b) + } + + /// Take gathered prints. + /// + pub fn take_prints(&mut self) -> Result> { + self.engine.take_prints() + } + + /// Clone a [`Engine`] + /// + /// To avoid having to parse same policy again, the engine can be cloned + /// after policies and data have been added. + fn clone(&self) -> Self { + Self { + engine: self.engine.clone(), + } + } } #[pymodule] diff --git a/bindings/python/test.py b/bindings/python/test.py index 014a77fc..7f7cef54 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -55,3 +55,40 @@ # Eval query as json results_json = engine.eval_query_as_json('data.framework.mount_overlay=x') print(results_json) + +# Eval rule +v = engine.eval_rule('data.framework.mount_overlay') +print(v) + +# Eval rule as json +v = engine.eval_rule_as_json('data.framework.mount_overlay') +print(v) + +# Enable coverage +engine.set_enable_coverage(True) +engine.eval_rule('data.framework.mount_overlay') + +# Print coverage +report_json = engine.get_coverage_report_as_json() +print(report_json) + +# Pretty coverage report +report = engine.get_coverage_report_pretty() +print(report) + +# Clone engine +engine1 = engine.clone() + + +# Clear coverage data +engine.clear_coverage_data(); + +print(engine1.get_coverage_report_pretty()) + +# Enable gathering prints +engine1.set_gather_prints(True) + +# Gather prints +engine1.eval_query('print("Hello")') +ps = engine1.take_prints() +print(ps) diff --git a/bindings/ruby/ext/regorusrb/Cargo.toml b/bindings/ruby/ext/regorusrb/Cargo.toml index b6888c77..c9cd25d7 100644 --- a/bindings/ruby/ext/regorusrb/Cargo.toml +++ b/bindings/ruby/ext/regorusrb/Cargo.toml @@ -9,8 +9,11 @@ publish = false crate-type = ["cdylib"] path = "src/lib.rs" +[features] +default = ["regorus/std", "regorus/full-opa"] + [dependencies] magnus = { version = "0.6.4" } -regorus = { git = "https://github.com/microsoft/regorus" } +regorus = { git = "https://github.com/microsoft/regorus", default-features = false, features = ["arc"] } serde_json = "1.0.117" serde_magnus = "0.8.1" diff --git a/bindings/ruby/lib/regorus/version.rb b/bindings/ruby/lib/regorus/version.rb index 72f7a284..4e639ff0 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.1.5" + VERSION = "0.1.0" end diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index ba588e03..6ff3a14d 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -10,8 +10,11 @@ keywords = ["interpreter", "opa", "policy-as-code", "rego"] [lib] crate-type = ["cdylib"] +[features] +default = ["regorus/std", "regorus/full-opa"] + [dependencies] -regorus = { path = "../.." } +regorus = { path = "../..", default-features = false, features = ["arc"] } serde_json = "1.0.111" wasm-bindgen = "0.2.90" diff --git a/bindings/wasm/README.md b/bindings/wasm/README.md index fb3e8703..902b01f3 100644 --- a/bindings/wasm/README.md +++ b/bindings/wasm/README.md @@ -10,72 +10,10 @@ See [Repository](https://github.com/microsoft/regorus). -To build this binding, see [building](https://github.com/microsoft/regorus/bindings/wasm/building.md) +To build this binding, see [building.md](https://github.com/microsoft/regorus/blob/main/bindings/wasm/building.md) ## Usage -```javascript - -var regorus = require('regorusjs') - -// Create an engine. -var engine = new regorus.Engine(); - -// Add Rego policy. -engine.add_policy( - // Associate this file name with policy - 'hello.rego', - - // Rego policy -` - package test - - # Join messages - message = concat(", ", [input.message, data.message]) -`) - -// Set policy data -engine.add_data_json(` - { - "message" : "World!" - } -`) - -// Set policy input -engine.set_input_json(` - { - "message" : "Hello" - } -`) - -// Eval query -results = engine.eval_query('data.test.message') - -// Display -console.log(results) -// { -// "result": [ -// { -// "expressions": [ -// { -// "value": "Hello, World!", -// "text": "data.test.message", -// "location": { -// "row": 1, -// "col": 1 -// } -// } -// ] -// } -// ] -// } - -// Convert results to object -results = JSON.parse(results) - -// Process result -console.log(results.result[0].expressions[0].value) -// Hello, World! -``` +See [test.js](https://github.com/microsoft/regorus/blob/main/bindings/wasm/test.js) for example usage. diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 397bb74f..64c5dd9f 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#![allow(non_snake_case)] + use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -50,7 +52,7 @@ impl Engine { /// /// * `path`: A filename to be associated with the policy. /// * `rego`: Rego policy. - pub fn add_policy(&mut self, path: String, rego: String) -> Result { + pub fn addPolicy(&mut self, path: String, rego: String) -> Result { self.engine.add_policy(path, rego).map_err(error_to_jsvalue) } @@ -58,15 +60,22 @@ impl Engine { /// /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data /// * `data`: JSON encoded value to be used as policy data. - pub fn add_data_json(&mut self, data: String) -> Result<(), JsValue> { + pub fn addDataJson(&mut self, data: String) -> Result<(), JsValue> { let data = regorus::Value::from_json_str(&data).map_err(error_to_jsvalue)?; self.engine.add_data(data).map_err(error_to_jsvalue) } + /// Get the list of packages defined by loaded policies. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages + pub fn getPackages(&self) -> Result, JsValue> { + self.engine.get_packages().map_err(error_to_jsvalue) + } + /// Clear policy data. /// /// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data - pub fn clear_data(&mut self) -> Result<(), JsValue> { + pub fn clearData(&mut self) -> Result<(), JsValue> { self.engine.clear_data(); Ok(()) } @@ -75,7 +84,7 @@ impl Engine { /// /// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_input /// * `input`: JSON encoded value to be used as input to query. - pub fn set_input_json(&mut self, input: String) -> Result<(), JsValue> { + pub fn setInputJson(&mut self, input: String) -> Result<(), JsValue> { let input = regorus::Value::from_json_str(&input).map_err(error_to_jsvalue)?; self.engine.set_input(input); Ok(()) @@ -85,13 +94,75 @@ impl Engine { /// /// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query /// * `query`: Rego expression to be evaluate. - pub fn eval_query(&mut self, query: String) -> Result { + pub fn evalQuery(&mut self, query: String) -> Result { let results = self .engine .eval_query(query, false) .map_err(error_to_jsvalue)?; serde_json::to_string_pretty(&results).map_err(error_to_jsvalue) } + + /// Evaluate rule(s) at given path. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.eval_rule + /// + /// * `path`: The full path to the rule(s). + pub fn evalRule(&mut self, path: String) -> Result { + let v = self.engine.eval_rule(path).map_err(error_to_jsvalue)?; + v.to_json_str().map_err(error_to_jsvalue) + } + + /// Gather output from print statements instead of emiting to stderr. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_gather_prints + /// * `b`: Whether to enable gathering prints or not. + pub fn setGatherPrints(&mut self, b: bool) { + self.engine.set_gather_prints(b) + } + + /// Take the gathered output of print statements. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.take_prints + pub fn takePrints(&mut self) -> Result, JsValue> { + self.engine.take_prints().map_err(error_to_jsvalue) + } + + /// Enable/disable policy coverage. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.set_enable_coverage + /// * `b`: Whether to enable gathering coverage or not. + pub fn setEnableCoverage(&mut self, enable: bool) { + self.engine.set_enable_coverage(enable) + } + + /// Get the coverage report as json. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_coverage_report + pub fn getCoverageReport(&self) -> Result { + let report = self + .engine + .get_coverage_report() + .map_err(error_to_jsvalue)?; + serde_json::to_string_pretty(&report).map_err(error_to_jsvalue) + } + + /// Clear gathered coverage data. + /// + /// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.clear_coverage_data + pub fn clearCoverageData(&mut self) { + self.engine.clear_coverage_data() + } + + /// Get ANSI color coded coverage report. + /// + /// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_pretty_string + pub fn getCoverageReportPretty(&self) -> Result { + let report = self + .engine + .get_coverage_report() + .map_err(error_to_jsvalue)?; + report.to_pretty_string().map_err(error_to_jsvalue) + } } #[cfg(test)] @@ -102,9 +173,10 @@ mod tests { #[wasm_bindgen_test] pub fn basic() -> Result<(), JsValue> { let mut engine = crate::Engine::new(); + engine.setEnableCoverage(true); // Exercise all APIs. - engine.add_data_json( + engine.addDataJson( r#" { "foo" : "bar" @@ -113,7 +185,7 @@ mod tests { .to_string(), )?; - engine.set_input_json( + engine.setInputJson( r#" { "message" : "Hello" @@ -122,15 +194,16 @@ mod tests { .to_string(), )?; - engine.add_policy( + let pkg = engine.addPolicy( "hello.rego".to_string(), r#" package test message = input.message"# .to_string(), )?; + assert_eq!(pkg, "data.test"); - let results = engine.eval_query("data".to_string())?; + let results = engine.evalQuery("data".to_string())?; let r = regorus::Value::from_json_str(&results).map_err(crate::error_to_jsvalue)?; let v = &r["result"][0]["expressions"][0]["value"]; @@ -141,6 +214,38 @@ mod tests { // Test that data was set. assert_eq!(v["foo"], regorus::Value::from("bar")); + // Use eval_rule to perform same query. + let v = engine.evalRule("data.test.message".to_owned())?; + let v = regorus::Value::from_json_str(&v).map_err(crate::error_to_jsvalue)?; + + // Ensure that input and policy were evaluated. + assert_eq!(v, regorus::Value::from("Hello")); + + let pkgs = engine.getPackages()?; + assert_eq!(pkgs, vec!["data.test"]); + + engine.setGatherPrints(true); + let _ = engine.evalQuery("print(\"Hello\")".to_owned()); + let prints = engine.takePrints()?; + assert_eq!(prints, vec![":1: Hello"]); + + // Test clone. + let mut engine1 = engine.clone(); + + // Test code coverage. + let report = engine1.getCoverageReport()?; + let r = regorus::Value::from_json_str(&report).map_err(crate::error_to_jsvalue)?; + + assert_eq!( + r["files"][0]["covered"] + .as_array() + .map_err(crate::error_to_jsvalue)?, + &vec![regorus::Value::from(3)] + ); + + println!("{}", engine1.getCoverageReportPretty()?); + + engine1.clearCoverageData(); Ok(()) } } diff --git a/bindings/wasm/test.js b/bindings/wasm/test.js index a0304bc2..b771de8f 100644 --- a/bindings/wasm/test.js +++ b/bindings/wasm/test.js @@ -1,48 +1,87 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -var regorus = require('./pkg/regorusjs') +var regorus = require('./pkg/regorusjs'); // Create an engine. var engine = new regorus.Engine(); +// Enable code coverage +engine.setEnableCoverage(true); + // Add Rego policy. -var pkg = engine.add_policy( +var pkg = engine.addPolicy( // Associate this file name with policy 'hello.rego', // Rego policy ` package test - + + x = 10 + # Join messages message = concat(", ", [input.message, data.message]) -`) -console.log("Loaded policy " + pkg) +`); + +console.log(pkg); +// data.test // Set policy data -engine.add_data_json(` +engine.addDataJson(` { "message" : "World!" } -`) +`); // Set policy input -engine.set_input_json(` +engine.setInputJson(` { "message" : "Hello" } -`) +`); + +// Eval rule as json +var value = engine.evalRule('data.test.message'); +value = JSON.parse(value); + +// Display value +console.log(value); +// Hello, World! // Eval query -var results = engine.eval_query('data.test.message') +results = engine.evalQuery('data.test.message'); // Display -console.log(results) +console.log(results); +// { +// "result": [ +// { +// "expressions": [ +// { +// "value": "Hello, World!", +// "text": "data.test.message", +// "location": { +// "row": 1, +// "col": 1 +// } +// } +// ] +// } +// ] +// } // Convert results to object -results = JSON.parse(results) +results = JSON.parse(results); // Process result -console.log(results.result[0].expressions[0].value) +console.log(results.result[0].expressions[0].value); +// Hello, World! + +// Print coverage report +report = engine.getCoverageReport(); +console.log(report); +// Print pretty report. +report = engine.getCoverageReportAsColoredString(); +console.log(report); diff --git a/examples/regorus.rs b/examples/regorus.rs index 44989b76..d02d409d 100644 --- a/examples/regorus.rs +++ b/examples/regorus.rs @@ -115,7 +115,7 @@ fn rego_eval( #[cfg(feature = "coverage")] if coverage { let report = engine.get_coverage_report()?; - println!("{}", report.to_colored_string()?); + println!("{}", report.to_pretty_string()?); } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 16c30d61..4cc1a334 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,7 +402,7 @@ pub mod coverage { /// /// - pub fn to_colored_string(&self) -> anyhow::Result { + pub fn to_pretty_string(&self) -> anyhow::Result { let mut s = String::default(); s.push_str("COVERAGE REPORT:\n"); for file in self.files.iter() { @@ -411,7 +411,7 @@ pub mod coverage { continue; } - s.push_str(&format!("{}:", file.path)); + s.push_str(&format!("{}:\n", file.path)); for (line, code) in file.code.split('\n').enumerate() { let line = line as u32 + 1; if file.not_covered.contains(&line) { diff --git a/tests/aci/main.rs b/tests/aci/main.rs index 6b4f0284..d25ea497 100644 --- a/tests/aci/main.rs +++ b/tests/aci/main.rs @@ -157,7 +157,7 @@ fn run_aci_tests_coverage(dir: &Path) -> Result<()> { } let report = engine.get_coverage_report()?; - println!("{}", report.to_colored_string()?); + println!("{}", report.to_pretty_string()?); Ok(()) } diff --git a/tests/kata/main.rs b/tests/kata/main.rs index c19eb911..5719b6a7 100644 --- a/tests/kata/main.rs +++ b/tests/kata/main.rs @@ -132,7 +132,7 @@ fn run_kata_tests( #[cfg(feature = "coverage")] { let report = engine.get_coverage_report()?; - println!("{}", report.to_colored_string()?); + println!("{}", report.to_pretty_string()?); } } }