From b5181546a04c88629b079455944a3601608776b8 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 - c, cpp - csharp - ffi - go - 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-ffi.yml | 22 +++ .github/workflows/test-java.yml | 4 +- .github/workflows/test-python.yml | 1 + .github/workflows/test-ruby.yml | 1 + .github/workflows/test-wasm.yml | 2 + .gitignore | 9 +- bindings/c-nostd/main.c | 14 +- bindings/c/CMakeLists.txt | 2 +- bindings/c/main.c | 53 ++++- bindings/cpp/CMakeLists.txt | 2 +- bindings/cpp/main.cpp | 18 +- bindings/cpp/regorus.hpp | 20 ++ bindings/csharp/Program.cs | 15 +- bindings/csharp/Regorus.cs | 139 ++++++++----- bindings/ffi/Cargo.toml | 3 +- bindings/ffi/RegorusFFI.g.cs | 87 --------- bindings/ffi/regorus.ffi.hpp | 99 ---------- bindings/ffi/regorus.h | 131 ------------- bindings/ffi/src/lib.rs | 162 +++++++++++++++- bindings/go/main.go | 41 +++- bindings/go/pkg/regorus/mod.go | 82 +++++++- bindings/java/Cargo.toml | 5 +- bindings/java/Test.java | 22 ++- bindings/java/com_microsoft_regorus_Engine.h | 72 ++++++- bindings/java/src/lib.rs | 183 ++++++++++++++++-- .../java/com/microsoft/regorus/Engine.java | 112 ++++++++++- bindings/python/Cargo.toml | 5 +- bindings/python/README.md | 13 +- bindings/python/src/lib.rs | 87 +++++++-- bindings/python/test.py | 43 +++- bindings/ruby/ext/regorusrb/Cargo.toml | 5 +- bindings/ruby/ext/regorusrb/src/lib.rs | 13 +- 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/engine.rs | 6 +- src/lib.rs | 4 +- tests/aci/main.rs | 2 +- tests/kata/main.rs | 2 +- 41 files changed, 1178 insertions(+), 564 deletions(-) create mode 100644 .github/workflows/test-ffi.yml delete mode 100644 bindings/ffi/RegorusFFI.g.cs delete mode 100644 bindings/ffi/regorus.ffi.hpp delete mode 100644 bindings/ffi/regorus.h diff --git a/.github/workflows/test-ffi.yml b/.github/workflows/test-ffi.yml new file mode 100644 index 00000000..991a327b --- /dev/null +++ b/.github/workflows/test-ffi.yml @@ -0,0 +1,22 @@ +name: bindings/c-cpp + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Test FFI + run: | + cargo build -r + cargo clippy --all-targets --no-deps -- -Dwarnings + working-directory: ./bindings/ffi diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml index 182287c0..56ff08fc 100644 --- a/.github/workflows/test-java.yml +++ b/.github/workflows/test-java.yml @@ -22,7 +22,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Building binding - run: cargo build --release --manifest-path bindings/java/Cargo.toml + run: | + cargo clippy --all-targets --no-deps -- -Dwarnings + cargo build --release --manifest-path bindings/java/Cargo.toml - name: Build jar run: mvn package diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 84a434d8..2581e140 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -34,4 +34,5 @@ jobs: run: | pip3 install dist/regorus-*.whl cd bindings/python + cargo clippy --all-targets --no-deps -- -Dwarnings python3 test.py diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 298e8bc0..2b7e3b76 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -27,4 +27,5 @@ jobs: - name: Run ruby tests run: | cd bindings/ruby + cargo clippy --all-targets --no-deps -- -Dwarnings bundle exec rake diff --git a/.github/workflows/test-wasm.yml b/.github/workflows/test-wasm.yml index 1a5e309e..9c2c4a36 100644 --- a/.github/workflows/test-wasm.yml +++ b/.github/workflows/test-wasm.yml @@ -26,5 +26,7 @@ jobs: - name: Test wasm binding run: | cd bindings/wasm + cargo clippy --all-targets --no-deps -- -Dwarnings wasm-pack build --target nodejs --release + wasm-pack test --release --node node test.js diff --git a/.gitignore b/.gitignore index 67bd23a4..8c2c60a1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,11 @@ Cargo.lock worktrees/ # build folders -**/build \ No newline at end of file +**/build + +# Generated C# bindings +**/*.g.cs + +# Generated C, C++ headers +bindings/ffi/regorus.h +bindings/ffi/regorus.ffi.hpp \ No newline at end of file diff --git a/bindings/c-nostd/main.c b/bindings/c-nostd/main.c index 9cd54e71..656fefdb 100644 --- a/bindings/c-nostd/main.c +++ b/bindings/c-nostd/main.c @@ -46,21 +46,21 @@ int main() { free(buffer); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); r = regorus_engine_add_policy(engine, "api.rego", (buffer = file_to_string("../../../tests/aci/api.rego"))); free(buffer); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); r = regorus_engine_add_policy(engine, "policy.rego", (buffer = file_to_string("../../../tests/aci/policy.rego"))); free(buffer); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); // Add data @@ -77,8 +77,8 @@ int main() { goto error; regorus_result_drop(r); - // Eval query - r = regorus_engine_eval_query(engine, "data.framework.mount_overlay=x"); + // Eval rule. + r = regorus_engine_eval_rule(engine, "data.framework.mount_overlay"); if (r.status != RegorusStatusOk) goto error; @@ -90,6 +90,10 @@ int main() { // Free the engine. regorus_engine_drop(engine); + if (num_allocations != 0) { + frintf(stderr, "not all allocations were freed."); + return -1; + } return 0; error: printf("%s", r.error_message); diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index d0fb422c..6acbb727 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -25,7 +25,7 @@ corrosion_import_crate( FEATURES "regorus/semver" # Link statically - CRATE_TYPES "staticlib" + CRATE_TYPES "cdylib" ) add_executable(regorus_test main.c) diff --git a/bindings/c/main.c b/bindings/c/main.c index 17651ba6..0066b39e 100644 --- a/bindings/c/main.c +++ b/bindings/c/main.c @@ -10,19 +10,19 @@ int main() { r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego"); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego"); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego"); if (r.status != RegorusStatusOk) goto error; - printf("Loaded policy %s\n", r.output); + printf("Loaded package %s\n", r.output); regorus_result_drop(r); // Add data @@ -37,22 +37,61 @@ int main() { goto error; regorus_result_drop(r); - // Eval query - r = regorus_engine_eval_query(engine, "data.framework.mount_overlay=x"); + // Eval rule. + r = regorus_engine_eval_query(engine, "data.framework.mount_overlay"); if (r.status != RegorusStatusOk) goto error; // Print output - printf("%s", r.output); + printf("%s\n", r.output); regorus_result_drop(r); - // Free the engine. regorus_engine_drop(engine); + // Create another engine. + engine = regorus_engine_new(); + + r = regorus_engine_add_policy( + engine, + "test.rego", + "package test\n" + "x = 1\n" + "message = `Hello`" + ); + + // Evaluate rule. + if (r.status != RegorusStatusOk) + goto error; + + r = regorus_engine_set_enable_coverage(engine, true); + regorus_result_drop(r); + + r = regorus_engine_eval_query(engine, "data.test.message"); + if (r.status != RegorusStatusOk) + goto error; + + // Print output + printf("%s\n", r.output); + regorus_result_drop(r); + + // Print pretty coverage report. + r = regorus_engine_get_coverage_report_pretty(engine); + if (r.status != RegorusStatusOk) + goto error; + + printf("%s\n", r.output); + regorus_result_drop(r); + + // Free the engine. + regorus_engine_drop(engine); + return 0; + error: printf("%s", r.error_message); + regorus_result_drop(r); + regorus_engine_drop(engine); return 1; } diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt index a510438b..04870fc0 100644 --- a/bindings/cpp/CMakeLists.txt +++ b/bindings/cpp/CMakeLists.txt @@ -26,7 +26,7 @@ corrosion_import_crate( FEATURES "regorus/semver" # Link statically - CRATE_TYPES "staticlib") + CRATE_TYPES "cdylib") add_executable(regorus_test main.cpp) # Add path to /bindings/ffi diff --git a/bindings/cpp/main.cpp b/bindings/cpp/main.cpp index 08145249..82672472 100644 --- a/bindings/cpp/main.cpp +++ b/bindings/cpp/main.cpp @@ -6,6 +6,8 @@ void example() // Create engine regorus::Engine engine; + engine.set_enable_coverage(true); + // Add policies. engine.add_policy("objects.rego",R"(package objects @@ -67,6 +69,14 @@ f := e["dev"])"); } else { std::cerr< -// This code is generated by csbindgen. -// DON'T CHANGE THIS DIRECTLY. -// -#pragma warning disable CS8500 -#pragma warning disable CS8981 -using System; -using System.Runtime.InteropServices; - - -namespace RegorusFFI -{ - internal static unsafe partial class API - { - const string __DllName = "regorus_ffi"; - - - - /// Drop a `RegorusResult`. `output` and `error_message` strings are not valid after drop. - [DllImport(__DllName, EntryPoint = "regorus_result_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void regorus_result_drop(RegorusResult r); - - /// Construct a new Engine See https://docs.rs/regorus/latest/regorus/struct.Engine.html - [DllImport(__DllName, EntryPoint = "regorus_engine_new", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusEngine* regorus_engine_new(); - - /// Clone a [`RegorusEngine`] To avoid having to parse same policy again, the engine can be cloned after policies and data have been added. - [DllImport(__DllName, EntryPoint = "regorus_engine_clone", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusEngine* regorus_engine_clone(RegorusEngine* engine); - - [DllImport(__DllName, EntryPoint = "regorus_engine_drop", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void regorus_engine_drop(RegorusEngine* engine); - - /// Add a policy The policy is parsed into AST. See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy * `path`: A filename to be associated with the policy. * `rego`: Rego policy. - [DllImport(__DllName, EntryPoint = "regorus_engine_add_policy", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_add_policy(RegorusEngine* engine, byte* path, byte* rego); - - [DllImport(__DllName, EntryPoint = "regorus_engine_add_policy_from_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_add_policy_from_file(RegorusEngine* engine, byte* path); - - /// Add policy data. See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data * `data`: JSON encoded value to be used as policy data. - [DllImport(__DllName, EntryPoint = "regorus_engine_add_data_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_add_data_json(RegorusEngine* engine, byte* data); - - [DllImport(__DllName, EntryPoint = "regorus_engine_add_data_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine* engine, byte* path); - - /// Clear policy data. See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data - [DllImport(__DllName, EntryPoint = "regorus_engine_clear_data", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_clear_data(RegorusEngine* engine); - - /// Set input. 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. - [DllImport(__DllName, EntryPoint = "regorus_engine_set_input_json", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_set_input_json(RegorusEngine* engine, byte* input); - - [DllImport(__DllName, EntryPoint = "regorus_engine_set_input_from_json_file", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine* engine, byte* path); - - /// Evaluate query. See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query * `query`: Rego expression to be evaluate. - [DllImport(__DllName, EntryPoint = "regorus_engine_eval_query", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern RegorusResult regorus_engine_eval_query(RegorusEngine* engine, byte* query); - - - } - - [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct RegorusResult - { - public RegorusStatus status; - public byte* output; - public byte* error_message; - } - - [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct RegorusEngine - { - } - - - internal enum RegorusStatus : uint - { - RegorusStatusOk, - RegorusStatusError, - } - - -} diff --git a/bindings/ffi/regorus.ffi.hpp b/bindings/ffi/regorus.ffi.hpp deleted file mode 100644 index 13a98bd5..00000000 --- a/bindings/ffi/regorus.ffi.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef REGORUS_FFI_HPP -#define REGORUS_FFI_HPP - -#include -#include -#include -#include -#include - -/// Status of a call on `RegorusEngine`. -enum class RegorusStatus { - /// The operation was successful. - RegorusStatusOk, - /// The operation was unsuccessful. - RegorusStatusError, -}; - -/// Wrapper for `regorus::Engine`. -struct RegorusEngine; - -/// Result of a call on `RegorusEngine`. -/// -/// Must be freed using `regorus_result_drop`. -struct RegorusResult { - /// Status - RegorusStatus status; - /// Output produced by the call. - /// Owned by Rust. - char *output; - /// Errors produced by the call. - /// Owned by Rust. - char *error_message; -}; - -extern "C" { - -/// Drop a `RegorusResult`. -/// -/// `output` and `error_message` strings are not valid after drop. -void regorus_result_drop(RegorusResult r); - -/// Construct a new Engine -/// -/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html -RegorusEngine *regorus_engine_new(); - -/// Clone a [`RegorusEngine`] -/// -/// To avoid having to parse same policy again, the engine can be cloned -/// after policies and data have been added. -RegorusEngine *regorus_engine_clone(RegorusEngine *engine); - -void regorus_engine_drop(RegorusEngine *engine); - -/// Add a policy -/// -/// The policy is parsed into AST. -/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy -/// -/// * `path`: A filename to be associated with the policy. -/// * `rego`: Rego policy. -RegorusResult regorus_engine_add_policy(RegorusEngine *engine, const char *path, const char *rego); - -RegorusResult regorus_engine_add_policy_from_file(RegorusEngine *engine, const char *path); - -/// Add policy data. -/// -/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data -/// * `data`: JSON encoded value to be used as policy data. -RegorusResult regorus_engine_add_data_json(RegorusEngine *engine, const char *data); - -RegorusResult regorus_engine_add_data_from_json_file(RegorusEngine *engine, const char *path); - -/// Clear policy data. -/// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data -RegorusResult regorus_engine_clear_data(RegorusEngine *engine); - -/// Set input. -/// -/// 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. -RegorusResult regorus_engine_set_input_json(RegorusEngine *engine, const char *input); - -RegorusResult regorus_engine_set_input_from_json_file(RegorusEngine *engine, const char *path); - -/// Evaluate query. -/// -/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query -/// * `query`: Rego expression to be evaluate. -RegorusResult regorus_engine_eval_query(RegorusEngine *engine, const char *query); - -extern uint8_t *regorus_aligned_alloc(uintptr_t alignment, uintptr_t size); - -extern void regorus_free(uint8_t *ptr); - -} // extern "C" - -#endif // REGORUS_FFI_HPP diff --git a/bindings/ffi/regorus.h b/bindings/ffi/regorus.h deleted file mode 100644 index db696b92..00000000 --- a/bindings/ffi/regorus.h +++ /dev/null @@ -1,131 +0,0 @@ -#ifndef REGORUS_H -#define REGORUS_H - -#include -#include -#include -#include - -/** - * Status of a call on `RegorusEngine`. - */ -typedef enum RegorusStatus { - /** - * The operation was successful. - */ - RegorusStatusOk, - /** - * The operation was unsuccessful. - */ - RegorusStatusError, -} RegorusStatus; - -/** - * Wrapper for `regorus::Engine`. - */ -typedef struct RegorusEngine RegorusEngine; - -/** - * Result of a call on `RegorusEngine`. - * - * Must be freed using `regorus_result_drop`. - */ -typedef struct RegorusResult { - /** - * Status - */ - enum RegorusStatus status; - /** - * Output produced by the call. - * Owned by Rust. - */ - char *output; - /** - * Errors produced by the call. - * Owned by Rust. - */ - char *error_message; -} RegorusResult; - -/** - * Drop a `RegorusResult`. - * - * `output` and `error_message` strings are not valid after drop. - */ -void regorus_result_drop(struct RegorusResult r); - -/** - * Construct a new Engine - * - * See https://docs.rs/regorus/latest/regorus/struct.Engine.html - */ -struct RegorusEngine *regorus_engine_new(void); - -/** - * Clone a [`RegorusEngine`] - * - * To avoid having to parse same policy again, the engine can be cloned - * after policies and data have been added. - */ -struct RegorusEngine *regorus_engine_clone(struct RegorusEngine *engine); - -void regorus_engine_drop(struct RegorusEngine *engine); - -/** - * Add a policy - * - * The policy is parsed into AST. - * See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_policy - * - * * `path`: A filename to be associated with the policy. - * * `rego`: Rego policy. - */ -struct RegorusResult regorus_engine_add_policy(struct RegorusEngine *engine, - const char *path, - const char *rego); - -struct RegorusResult regorus_engine_add_policy_from_file(struct RegorusEngine *engine, - const char *path); - -/** - * Add policy data. - * - * See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_data - * * `data`: JSON encoded value to be used as policy data. - */ -struct RegorusResult regorus_engine_add_data_json(struct RegorusEngine *engine, const char *data); - -struct RegorusResult regorus_engine_add_data_from_json_file(struct RegorusEngine *engine, - const char *path); - -/** - * Clear policy data. - * - * See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.clear_data - */ -struct RegorusResult regorus_engine_clear_data(struct RegorusEngine *engine); - -/** - * Set input. - * - * 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. - */ -struct RegorusResult regorus_engine_set_input_json(struct RegorusEngine *engine, const char *input); - -struct RegorusResult regorus_engine_set_input_from_json_file(struct RegorusEngine *engine, - const char *path); - -/** - * Evaluate query. - * - * See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.eval_query - * * `query`: Rego expression to be evaluate. - */ -struct RegorusResult regorus_engine_eval_query(struct RegorusEngine *engine, const char *query); - -extern uint8_t *regorus_aligned_alloc(uintptr_t alignment, uintptr_t size); - -extern void regorus_free(uint8_t *ptr); - -#endif /* REGORUS_H */ diff --git a/bindings/ffi/src/lib.rs b/bindings/ffi/src/lib.rs index 890214f1..4d9298ad 100644 --- a/bindings/ffi/src/lib.rs +++ b/bindings/ffi/src/lib.rs @@ -119,21 +119,20 @@ pub extern "C" fn regorus_engine_new() -> *mut RegorusEngine { /// /// To avoid having to parse same policy again, the engine can be cloned /// after policies and data have been added. +/// #[no_mangle] pub extern "C" fn regorus_engine_clone(engine: *mut RegorusEngine) -> *mut RegorusEngine { - unsafe { - if engine.is_null() { - return std::ptr::null_mut(); - } - Box::into_raw(Box::new((*engine).clone())) + match to_ref(&engine) { + Ok(e) => Box::into_raw(Box::new(e.clone())), + _ => std::ptr::null_mut(), } } #[no_mangle] pub extern "C" fn regorus_engine_drop(engine: *mut RegorusEngine) { - if !engine.is_null() { + if let Ok(e) = to_ref(&engine) { unsafe { - let _ = Box::from_raw(engine); + let _ = Box::from_raw(std::ptr::from_mut(e)); } } } @@ -187,6 +186,18 @@ pub extern "C" fn regorus_engine_add_data_json( }()) } +/// Get list of loaded Rego packages as JSON. +/// +/// See https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.get_packages +/// * `data`: JSON encoded value to be used as policy data. +#[no_mangle] +pub extern "C" fn regorus_engine_get_packages(engine: *mut RegorusEngine) -> RegorusResult { + to_regorus_string_result(|| -> Result { + serde_json::to_string_pretty(&to_ref(&engine)?.engine.get_packages()?) + .map_err(anyhow::Error::msg) + }()) +} + #[cfg(feature = "std")] #[no_mangle] pub extern "C" fn regorus_engine_add_data_from_json_file( @@ -196,7 +207,7 @@ pub extern "C" fn regorus_engine_add_data_from_json_file( to_regorus_result(|| -> Result<()> { to_ref(&engine)? .engine - .add_data(regorus::Value::from_json_file(&from_c_str("path", path)?)?) + .add_data(regorus::Value::from_json_file(from_c_str("path", path)?)?) }()) } @@ -237,7 +248,7 @@ pub extern "C" fn regorus_engine_set_input_from_json_file( to_regorus_result(|| -> Result<()> { to_ref(&engine)? .engine - .set_input(regorus::Value::from_json_file(&from_c_str("path", path)?)?); + .set_input(regorus::Value::from_json_file(from_c_str("path", path)?)?); Ok(()) }()) } @@ -267,6 +278,139 @@ 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 +/// * `rule`: Path to the rule. +#[no_mangle] +pub extern "C" fn regorus_engine_eval_rule( + engine: *mut RegorusEngine, + rule: *const c_char, +) -> RegorusResult { + let output = || -> Result { + to_ref(&engine)? + .engine + .eval_rule(from_c_str("rule", rule)?)? + .to_json_str() + }(); + match output { + Ok(out) => RegorusResult { + status: RegorusStatus::RegorusStatusOk, + output: to_c_str(out), + error_message: std::ptr::null_mut(), + }, + Err(e) => to_regorus_result(Err(e)), + } +} + +/// Enable/disable coverage. +/// +/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.set_enable_coverage +/// * `enable`: Whether to enable or disable coverage. +#[no_mangle] +#[cfg(feature = "coverage")] +pub extern "C" fn regorus_engine_set_enable_coverage( + engine: *mut RegorusEngine, + enable: bool, +) -> RegorusResult { + to_regorus_result(|| -> Result<()> { + to_ref(&engine)?.engine.set_enable_coverage(enable); + Ok(()) + }()) +} + +/// Get coverage report. +/// +/// See https://docs.rs/regorus/0.1.0-alpha.2/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 { + let output = || -> Result { + Ok(serde_json::to_string_pretty( + &to_ref(&engine)?.engine.get_coverage_report()?, + )?) + }(); + match output { + Ok(out) => RegorusResult { + status: RegorusStatus::RegorusStatusOk, + output: to_c_str(out), + error_message: std::ptr::null_mut(), + }, + Err(e) => to_regorus_result(Err(e)), + } +} + +/// Get pretty printed coverage report. +/// +/// See https://docs.rs/regorus/latest/regorus/coverage/struct.Report.html#method.to_string_pretty +#[no_mangle] +#[cfg(feature = "coverage")] +pub extern "C" fn regorus_engine_get_coverage_report_pretty( + engine: *mut RegorusEngine, +) -> RegorusResult { + let output = || -> Result { + to_ref(&engine)? + .engine + .get_coverage_report()? + .to_string_pretty() + }(); + match output { + Ok(out) => RegorusResult { + status: RegorusStatus::RegorusStatusOk, + output: to_c_str(out), + error_message: std::ptr::null_mut(), + }, + Err(e) => to_regorus_result(Err(e)), + } +} + +/// Clear coverage data. +/// +/// See https://docs.rs/regorus/0.1.0-alpha.2/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 { + to_regorus_result(|| -> Result<()> { + to_ref(&engine)?.engine.clear_coverage_data(); + Ok(()) + }()) +} + +/// 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 +/// * `enable`: Whether to enable or disable gathering print statements. +#[no_mangle] +pub extern "C" fn regorus_engine_set_gather_prints( + engine: *mut RegorusEngine, + enable: bool, +) -> RegorusResult { + to_regorus_result(|| -> Result<()> { + to_ref(&engine)?.engine.set_gather_prints(enable); + Ok(()) + }()) +} + +/// Take all the gathered print statements. +/// +/// See https://docs.rs/regorus/0.1.0-alpha.2/regorus/struct.Engine.html#method.take_prints +#[no_mangle] +pub extern "C" fn regorus_engine_take_prints(engine: *mut RegorusEngine) -> RegorusResult { + let output = || -> Result { + Ok(serde_json::to_string_pretty( + &to_ref(&engine)?.engine.take_prints()?, + )?) + }(); + match output { + Ok(out) => RegorusResult { + status: RegorusStatus::RegorusStatusOk, + output: to_c_str(out), + 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 05c8a1a3..eb2ac321 100644 --- a/bindings/go/main.go +++ b/bindings/go/main.go @@ -12,7 +12,7 @@ func main() { var err error t := time.Now(); - + // Create new engine engine := regorus.NewEngine() defer engine.Close() @@ -26,10 +26,12 @@ func main() { "../../tests/aci/policy.rego", } for _, policy := range policies { - if err := engine.AddPolicyFromFile(policy); err != nil { + var pkg string + if pkg, err = engine.AddPolicyFromFile(policy); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } + fmt.Printf("Loaded package %s\n", pkg); } if err = engine.AddDataFromJsonFile("../../tests/aci/data.json"); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) @@ -38,21 +40,48 @@ func main() { elapsed2 := time.Since(t) t = time.Now() - // Set input and eval query. + // Set input. if err = engine.SetInputFromJsonFile("../../tests/aci/input.json"); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } - - if output, err = engine.EvalQuery("data.framework.mount_overlay = x"); err != nil { + // Eval Rule + if output, err = engine.EvalRule("data.framework.mount_overlay"); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } elapsed3 := time.Since(t) - fmt.Println("{%s}", output) + fmt.Printf("%s\n", output) fmt.Printf("NewEngine took %v\n", elapsed1) fmt.Printf("Add policies and data took %v\n", elapsed2) fmt.Printf("Set input and eval query took %v\n", elapsed3) + + // Create new engine. + engine1 := regorus.NewEngine() + defer engine1.Close() + + // Enable coverage + engine1.SetEnableCoverage(true) + + var pkg string + pkg, err = engine1.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`") + fmt.Printf("Loaded package %s\n", pkg) + + // Eval Rule + if output, err = engine1.EvalRule("data.test.message"); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("%s\n", output) + + // Print pretty coverage report. + if output, err = engine1.GetCoverageReportPretty(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + + fmt.Printf("%s\n", output) } diff --git a/bindings/go/pkg/regorus/mod.go b/bindings/go/pkg/regorus/mod.go index e1529ef5..55d7da6e 100644 --- a/bindings/go/pkg/regorus/mod.go +++ b/bindings/go/pkg/regorus/mod.go @@ -28,7 +28,7 @@ func (e *Engine) Clone() *Engine { return c } -func (e *Engine) AddPolicy(path string, rego string) error { +func (e *Engine) AddPolicy(path string, rego string) (string, error) { path_c := C.CString(path) defer C.free(unsafe.Pointer(path_c)) @@ -38,21 +38,21 @@ func (e *Engine) AddPolicy(path string, rego string) error { result := C.regorus_engine_add_policy(e.e, path_c, rego_c) defer C.regorus_result_drop(result) if result.status != C.RegorusStatusOk { - return fmt.Errorf("%s", C.GoString(result.error_message)) + return "", fmt.Errorf("%s", C.GoString(result.error_message)) } - return nil + return C.GoString(result.output), nil } -func (e *Engine) AddPolicyFromFile(path string) error { +func (e *Engine) AddPolicyFromFile(path string) (string, error) { path_c := C.CString(path) defer C.free(unsafe.Pointer(path_c)) result := C.regorus_engine_add_policy_from_file(e.e, path_c) defer C.regorus_result_drop(result) if result.status != C.RegorusStatusOk { - return fmt.Errorf("%s", C.GoString(result.error_message)) + return "", fmt.Errorf("%s", C.GoString(result.error_message)) } - return nil + return C.GoString(result.output), nil } func (e *Engine) AddDataJson(data string) error { @@ -115,3 +115,73 @@ func (e *Engine) EvalQuery(query string) (string, error) { return C.GoString(result.output), nil } + +func (e *Engine) EvalRule(rule string) (string, error) { + rule_c := C.CString(rule) + defer C.free(unsafe.Pointer(rule_c)) + + result := C.regorus_engine_eval_rule(e.e, rule_c) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + + return C.GoString(result.output), nil +} + +func (e *Engine) SetEnableCoverage(enable bool) error { + result := C.regorus_engine_set_enable_coverage(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) ClearCoverageData() error { + result := C.regorus_engine_clear_coverage_data(e.e) + 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) GetCoverageReport() (string, error) { + result := C.regorus_engine_get_coverage_report(e.e) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + + return C.GoString(result.output), nil +} + +func (e *Engine) GetCoverageReportPretty() (string, error) { + result := C.regorus_engine_get_coverage_report_pretty(e.e) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + + return C.GoString(result.output), nil +} + +func (e *Engine) SetGatherPrints(b bool) error { + result := C.regorus_engine_set_gather_prints(e.e, C.bool(b)) + 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) TakePrints() (string, error) { + result := C.regorus_engine_take_prints(e.e) + defer C.regorus_result_drop(result) + if result.status != C.RegorusStatusOk { + return "", fmt.Errorf("%s", C.GoString(result.error_message)) + } + + return C.GoString(result.output), nil +} 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..3940f013 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( + String 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); + + String coverageJson = engine.getCoverageReport(); + System.out.println(coverageJson); + + 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..f09b07b2 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] @@ -117,7 +157,7 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromF let _ = throw_err(env, |env| { let engine = unsafe { &mut *(engine_ptr as *mut Engine) }; let path: String = env.get_string(&path)?.into(); - engine.set_input(Value::from_json_file(&path)?); + engine.set_input(Value::from_json_file(path)?); Ok(()) }); } @@ -144,12 +184,133 @@ pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery( } #[no_mangle] -pub unsafe extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine( +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_string_pretty()?; + 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 extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine( _env: JNIEnv, _class: JClass, engine_ptr: jlong, ) { - let _engine = Box::from_raw(engine_ptr as *mut Engine); + unsafe { + let _engine = Box::from_raw(engine_ptr as *mut Engine); + } } fn throw_err(mut env: JNIEnv, mut f: impl FnMut(&mut JNIEnv) -> Result) -> Result { 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..0766633f 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -21,19 +21,7 @@ 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 { +fn from(ob: &Bound<'_, PyAny>) -> Result { // dicts Ok(if let Ok(dict) = ob.downcast::() { let mut map = BTreeMap::new(); @@ -141,7 +129,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result { Value::Array(_) => { let list = PyList::empty_bound(py); - for v in std::mem::replace(v.as_array_mut()?, Vec::new()) { + for v in std::mem::take(v.as_array_mut()?) { list.append(to(v, py)?)?; } list.into() @@ -149,7 +137,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result { Value::Set(_) => { let set = PySet::empty_bound(py)?; - for v in std::mem::replace(v.as_set_mut()?, BTreeSet::new()) { + for v in std::mem::take(v.as_set_mut()?) { set.add(to(v, py)?)?; } set.into() @@ -157,7 +145,7 @@ fn to(mut v: Value, py: Python<'_>) -> Result { Value::Object(_) => { let dict = PyDict::new_bound(py); - for (k, v) in std::mem::replace(v.as_object_mut()?, BTreeMap::new()) { + for (k, v) in std::mem::take(v.as_object_mut()?) { dict.set_item(to(k, py)?, to(v, py)?)?; } dict.into() @@ -221,7 +209,7 @@ impl Engine { /// /// * `path`: Path to JSON policy data. pub fn add_data_from_json_file(&mut self, path: String) -> Result<()> { - let data = Value::from_json_file(&path)?; + let data = Value::from_json_file(path)?; self.engine.add_data(data) } @@ -254,7 +242,7 @@ impl Engine { /// /// * `path`: Path to JSON input data. pub fn set_input_from_json_file(&mut self, path: String) -> Result<()> { - let input = Value::from_json_file(&path)?; + let input = Value::from_json_file(path)?; self.engine.set_input(input); Ok(()) } @@ -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_string_pretty() + } + + /// 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..6ea61634 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -8,13 +8,13 @@ # Load policies pkg = engine.add_policy_from_file('../../tests/aci/framework.rego') -print(' Loaded Policy %s' % pkg) +print(' Loaded package %s' % pkg) pkg = engine.add_policy_from_file('../../tests/aci/api.rego') -print(' Loaded Policy %s' % pkg) +print(' Loaded package %s' % pkg) pkg = engine.add_policy_from_file('../../tests/aci/policy.rego') -print(' Loaded Policy %s' % pkg) +print(' Loaded package %s' % pkg) # Add policy data data = { @@ -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/ext/regorusrb/src/lib.rs b/bindings/ruby/ext/regorusrb/src/lib.rs index 6888e489..27dfcb36 100644 --- a/bindings/ruby/ext/regorusrb/src/lib.rs +++ b/bindings/ruby/ext/regorusrb/src/lib.rs @@ -72,7 +72,7 @@ impl Engine { } fn add_data_from_json_file(&self, path: String) -> Result<(), Error> { - let json_data = regorus::Value::from_json_file(&path).map_err(|e| { + let json_data = regorus::Value::from_json_file(path).map_err(|e| { Error::new( runtime_error(), format!("Failed to parse JSON data file: {}", e), @@ -112,7 +112,7 @@ impl Engine { } fn add_input_from_json_file(&self, path: String) -> Result<(), Error> { - let json_data = regorus::Value::from_json_file(&path).map_err(|e| { + let json_data = regorus::Value::from_json_file(path).map_err(|e| { Error::new( runtime_error(), format!("Failed to parse JSON input file: {}", e), @@ -193,7 +193,8 @@ impl Engine { } fn set_enable_coverage(&self, enable: bool) -> Result<(), Error> { - Ok(self.engine.borrow_mut().set_enable_coverage(enable)) + self.engine.borrow_mut().set_enable_coverage(enable); + Ok(()) } fn get_coverage_report_as_json(&self) -> Result { @@ -237,12 +238,14 @@ impl Engine { } fn clear_coverage_data(&self) -> Result<(), Error> { - Ok(self.engine.borrow_mut().clear_coverage_data()) + self.engine.borrow_mut().clear_coverage_data(); + Ok(()) } // Print statements can be gathered async instead of printing to stderr fn set_gather_prints(&self, enable: bool) -> Result<(), Error> { - Ok(self.engine.borrow_mut().set_gather_prints(enable)) + self.engine.borrow_mut().set_gather_prints(enable); + Ok(()) } fn take_prints(&self) -> Result, Error> { 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..0d09448e 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_string_pretty + pub fn getCoverageReportPretty(&self) -> Result { + let report = self + .engine + .get_coverage_report() + .map_err(error_to_jsvalue)?; + report.to_string_pretty().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..61a06770 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.getCoverageReportPretty(); +console.log(report); diff --git a/examples/regorus.rs b/examples/regorus.rs index 44989b76..6e046f1b 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_string_pretty()?); } Ok(()) diff --git a/src/engine.rs b/src/engine.rs index f9b164f8..8df331a2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -240,7 +240,7 @@ impl Engine { &self.modules } - /// Evaluate rule(s) at given path. + /// Evaluate specified rule(s). /// /// [`Engine::eval_rule`] is often faster than [`Engine::eval_query`] and should be preferred if /// OPA style [`QueryResults`] are not needed. @@ -280,10 +280,10 @@ impl Engine { /// # Ok(()) /// # } /// ``` - pub fn eval_rule(&mut self, path: String) -> Result { + pub fn eval_rule(&mut self, rule: String) -> Result { self.prepare_for_eval(false)?; self.interpreter.clean_internal_evaluation_state(); - self.interpreter.eval_rule_in_path(path) + self.interpreter.eval_rule_in_path(rule) } /// Evaluate a Rego query. diff --git a/src/lib.rs b/src/lib.rs index 16c30d61..58e522f0 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_string_pretty(&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..3b773131 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_string_pretty()?); Ok(()) } diff --git a/tests/kata/main.rs b/tests/kata/main.rs index c19eb911..7b6f3b30 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_string_pretty()?); } } }